Skip to main content

rupg_catalog/
owner.rs

1//! The change of the owner of an object: `ATExecChangeOwner` for one relation and `AlterSchemaOwner_internal` for a schema, with `aclnewowner` for the privileges. The analyzer runs the checks and the walk to the indexes, the TOAST table and the sequences of a table.
2
3use std::sync::Arc;
4
5use rupg_common::Result;
6
7use super::{Catalog, no_object};
8use crate::{
9    ObjRef, PG_COLLATION, PG_EXTENSION, PG_LANGUAGE, PG_OPCLASS, PG_OPERATOR, PG_OPFAMILY, PG_PROC,
10    PG_STATISTIC_EXT, PG_TS_CONFIG, PG_TS_DICT, PG_TYPE,
11};
12
13/// `ACL_ALL_RIGHTS_STR`: the letter of each privilege, in the order of its bit.
14const RIGHTS: &[u8] = b"arwdDxtXUCTcsAm";
15
16/// One `aclitem` in its parts: the grantee, the grantor, and the bits of the privileges and of their grant options. An empty grantee is `PUBLIC`.
17struct Item {
18    grantee: String,
19    grantor: String,
20    rights: u32,
21    options: u32,
22}
23
24impl Item {
25    /// The parts of the text form `grantee=privileges/grantor`. The names of the built-in roles need no quotes, so the parts are the text between `=` and `/`.
26    fn parse(text: &str) -> Option<Item> {
27        let (grantee, rest) = text.split_once('=')?;
28        let (privileges, grantor) = rest.rsplit_once('/')?;
29        let (mut rights, mut options) = (0, 0);
30        let mut last = None;
31        for b in privileges.bytes() {
32            if b == b'*' {
33                options |= last?;
34                continue;
35            }
36            let bit = 1 << RIGHTS.iter().position(|&r| r == b)?;
37            rights |= bit;
38            last = Some(bit);
39        }
40        Some(Item { grantee: grantee.into(), grantor: grantor.into(), rights, options })
41    }
42
43    /// The text form, as `aclitemout` writes it.
44    fn text(&self) -> String {
45        let mut out = format!("{}=", self.grantee);
46        for (i, &letter) in RIGHTS.iter().enumerate() {
47            if self.rights & (1 << i) != 0 {
48                out.push(char::from(letter));
49                if self.options & (1 << i) != 0 {
50                    out.push('*');
51                }
52            }
53        }
54        out.push('/');
55        out.push_str(&self.grantor);
56        out
57    }
58}
59
60/// `aclnewowner`: the items with the old owner as grantee or grantor get the new owner. When the new owner was in the list before, two items with the same grantee and grantor become one, and an item with no privileges goes away. An item that is not in the text form stays as it is.
61pub fn acl_new_owner(acl: &[String], old: &str, new: &str) -> Vec<String> {
62    let mut items: Vec<(Option<Item>, &String)> = Vec::with_capacity(acl.len());
63    let mut new_present = false;
64    for text in acl {
65        let mut item = Item::parse(text);
66        if let Some(item) = &mut item {
67            for name in [&mut item.grantee, &mut item.grantor] {
68                if name == new {
69                    new_present = true;
70                } else if name == old {
71                    *name = new.to_string();
72                }
73            }
74        }
75        items.push((item, text));
76    }
77    if new_present {
78        for dst in 0..items.len() {
79            let Some(item) = &items[dst].0 else { continue };
80            if item.rights == 0 {
81                continue;
82            }
83            let (grantee, grantor) = (item.grantee.clone(), item.grantor.clone());
84            let (mut rights, mut options) = (0, 0);
85            for (src, _) in items.iter_mut().skip(dst + 1) {
86                if let Some(src) = src
87                    && src.grantee == grantee
88                    && src.grantor == grantor
89                {
90                    rights |= src.rights;
91                    options |= src.options;
92                    src.rights = 0;
93                    src.options = 0;
94                }
95            }
96            if let Some(item) = &mut items[dst].0 {
97                item.rights |= rights;
98                item.options |= options;
99            }
100        }
101        items.retain(|(item, _)| !matches!(item, Some(item) if item.rights == 0));
102    }
103    items
104        .into_iter()
105        .map(|(item, text)| item.map_or_else(|| text.clone(), |item| item.text()))
106        .collect()
107}
108
109impl Catalog {
110    /// The change of `ATExecChangeOwner` for one relation: `relowner`, `relacl`, and the owner of the row type and of its array type, as `AlterTypeOwnerInternal` changes them. `old` and `new` are the names of the two roles, for the items of the ACL.
111    pub fn set_relation_owner(&mut self, oid: u32, owner: u32, old: &str, new: &str) -> Result<()> {
112        let rel = self.relation_mut(oid)?;
113        rel.owner = owner;
114        if let Some(acl) = &rel.acl {
115            rel.acl = Some(acl_new_owner(acl, old, new));
116        }
117        let row_type = rel.row_type;
118        let array = match self.types.get_mut(&row_type) {
119            Some(ty) => {
120                ty.owner = owner;
121                ty.array
122            }
123            None => 0,
124        };
125        if let Some(ty) = self.types.get_mut(&array) {
126            ty.owner = owner;
127        }
128        Ok(())
129    }
130
131    /// The change of `AlterSchemaOwner_internal`: `nspowner` and `nspacl`. `old` and `new` are the names of the two roles, for the items of the ACL.
132    pub fn set_schema_owner(&mut self, oid: u32, owner: u32, old: &str, new: &str) -> Result<()> {
133        let schema = self.schemas.get_mut(&oid).ok_or_else(|| no_object("schema", oid))?;
134        schema.owner = owner;
135        if let Some(acl) = &schema.acl {
136            schema.acl = Some(acl_new_owner(acl, old, new));
137        }
138        Ok(())
139    }
140
141    /// The change of `AlterObjectOwner_internal` for a function, a collation, a language, an extension, an operator, an operator class, an operator family, a statistics object, a text search dictionary and a text search configuration, and of `AlterTypeOwnerInternal` for the ACL of a type. The owner column changes, and the ACL that `GRANT` gave gets `aclnewowner`. The caller changes the owner of a type with [`Catalog::set_type_owner`] first. `old` and `new` are the names of the two roles, for the items of the ACL.
142    ///
143    /// # Errors
144    ///
145    /// An internal error for an object that the catalog does not have.
146    pub fn set_object_owner(
147        &mut self,
148        object: ObjRef,
149        owner: u32,
150        old: &str,
151        new: &str,
152    ) -> Result<()> {
153        let found = match object.class {
154            PG_TYPE => true,
155            PG_PROC => self.functions.get_mut(&object.oid).map(|f| f.owner = owner).is_some(),
156            PG_COLLATION => match Arc::make_mut(&mut self.collations).get_mut(&object.oid) {
157                Some(c) => {
158                    c.owner = owner;
159                    true
160                }
161                None => false,
162            },
163            PG_LANGUAGE => self.languages.get_mut(&object.oid).map(|l| l.owner = owner).is_some(),
164            PG_EXTENSION => self.extensions.get_mut(&object.oid).map(|e| e.owner = owner).is_some(),
165            PG_OPERATOR => self.operators.get_mut(&object.oid).map(|o| o.owner = owner).is_some(),
166            PG_OPCLASS => self.opclasses.get_mut(&object.oid).map(|c| c.owner = owner).is_some(),
167            PG_OPFAMILY => self.opfamilies.get_mut(&object.oid).map(|f| f.owner = owner).is_some(),
168            PG_STATISTIC_EXT => {
169                self.stat_exts.get_mut(&object.oid).map(|s| s.owner = owner).is_some()
170            }
171            PG_TS_DICT => {
172                self.ts_dictionaries.get_mut(&object.oid).map(|d| d.owner = owner).is_some()
173            }
174            PG_TS_CONFIG => self.ts_configs.get_mut(&object.oid).map(|c| c.owner = owner).is_some(),
175            _ => false,
176        };
177        if !found {
178            return Err(no_object("object", object.oid));
179        }
180        if let Some(acl) = self.object_acl(object) {
181            let acl = acl_new_owner(acl, old, new);
182            self.set_object_acl(object, Some(acl));
183        }
184        Ok(())
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    fn acl(items: &[&str]) -> Vec<String> {
193        items.iter().map(|s| s.to_string()).collect()
194    }
195
196    #[test]
197    fn the_new_owner_takes_the_items_of_the_old_owner() {
198        let old = acl(&["alice=arwdDxtm/alice", "=r/alice", "bob=r*/alice"]);
199        assert_eq!(
200            acl_new_owner(&old, "alice", "carol"),
201            ["carol=arwdDxtm/carol", "=r/carol", "bob=r*/carol"]
202        );
203    }
204
205    #[test]
206    fn the_items_of_the_new_owner_merge() {
207        let old = acl(&["alice=arwdDxtm/alice", "bob=r/alice", "bob=w*/bob"]);
208        assert_eq!(acl_new_owner(&old, "alice", "bob"), ["bob=arw*dDxtm/bob"]);
209        let old = acl(&["bob=r/alice", "bob=a*/bob", "alice=UC/alice"]);
210        assert_eq!(acl_new_owner(&old, "alice", "bob"), ["bob=a*rUC/bob"]);
211        let old = acl(&["=U/alice", "bob=X/carol"]);
212        assert_eq!(acl_new_owner(&old, "alice", "bob"), ["=U/bob", "bob=X/carol"]);
213    }
214}