Skip to main content

rupg_analyze/
perm.rs

1//! `RTEPermissionInfo`: the privileges that a statement needs on the relations that it reads and writes, which `ExecCheckPermissions` checks before the statement runs.
2
3use std::collections::BTreeSet;
4
5use rupg_catalog::RelKind;
6
7use crate::Analyzer;
8use crate::expr::{Expr, ExprKind};
9use crate::insert::Given;
10use crate::select::Query;
11
12/// `ACL_INSERT`.
13pub(crate) const ACL_INSERT: u32 = 1 << 0;
14/// `ACL_SELECT`.
15pub(crate) const ACL_SELECT: u32 = 1 << 1;
16/// `ACL_UPDATE`, which is also `ACL_SELECT_FOR_UPDATE`.
17pub(crate) const ACL_UPDATE: u32 = 1 << 2;
18/// `ACL_DELETE`.
19pub(crate) const ACL_DELETE: u32 = 1 << 3;
20
21/// `RTEPermissionInfo`: the privileges that the statement needs on a relation.
22#[derive(Clone, Debug, Default, PartialEq, Eq)]
23pub struct PermInfo {
24    /// The OID of the relation.
25    pub relid: u32,
26    /// `checkAsUser`: the role whose privileges count, or 0 for the current user. A relation of the query of a view uses the owner of the view, unless the view has `security_invoker`.
27    pub check_as: u32,
28    /// True for a relation of the query of a view with `security_invoker`, whose 0 in `check_as` stays the current user in a view outside it, as `RelationBuildRuleLock` gives `InvalidOid` to `setRuleCheckAsUser` for the rules of the view.
29    pub invoker: bool,
30    /// `requiredPerms`: the bits of `ACL_SELECT`, `ACL_INSERT`, `ACL_UPDATE` and `ACL_DELETE` that the statement needs.
31    pub required: u32,
32    /// `selectedCols`: the numbers of the columns that the statement reads, with 0 for the whole row.
33    pub selected: BTreeSet<i16>,
34    /// `insertedCols`: the numbers of the columns that an `INSERT` gives values.
35    pub inserted: BTreeSet<i16>,
36    /// `updatedCols`: the numbers of the columns that an `UPDATE` sets.
37    pub updated: BTreeSet<i16>,
38}
39
40/// Adds `offset` to the index of the privileges of each relation of the query and of its subqueries in `FROM`, after the privileges of the query of a view go after the privileges of the statement.
41fn shift(query: &mut Query, offset: usize) {
42    for relation in &mut query.relations {
43        if let Some(perm) = &mut relation.perm {
44            *perm += offset;
45        }
46        if let Some(sub) = relation.subquery.as_deref_mut() {
47            shift(sub, offset);
48        }
49    }
50}
51
52/// Forgets the privileges of the relations of the query and of its subqueries in `FROM`, for the query of a view that the statement does not read.
53pub(crate) fn forget(query: &mut Query) {
54    for relation in &mut query.relations {
55        relation.perm = None;
56        if let Some(sub) = relation.subquery.as_deref_mut() {
57            forget(sub);
58        }
59    }
60}
61
62/// True when the boolean option `name` of `reloptions` is on, as `RelationHasSecurityInvoker` and `RelationIsSecurityView` read `security_invoker` and `security_barrier`.
63pub fn option_on(options: &[String], name: &str) -> bool {
64    options.iter().any(|option| {
65        option.split_once('=').is_some_and(|(option, value)| {
66            option == name
67                && matches!(
68                    value.to_ascii_lowercase().as_str(),
69                    "true" | "on" | "yes" | "1" | "t" | "y"
70                )
71        })
72    })
73}
74
75impl Analyzer<'_> {
76    /// `addRTEPermissionInfo`: a new entry of the privileges of the statement for the relation, which needs the bits `required`. It gives the index of the entry.
77    pub(crate) fn add_perm(&mut self, relid: u32, required: u32) -> usize {
78        self.perms.push(PermInfo { relid, required, ..PermInfo::default() });
79        self.perms.len() - 1
80    }
81
82    /// The part of `ApplyRetrieveRule` for the privileges: the privileges of the query of the view `view` go after the privileges of the statement, and they are the privileges of the owner of the view, unless the view has `security_invoker`.
83    pub(crate) fn add_view_perms(&mut self, view: u32, query: &mut Query, perms: Vec<PermInfo>) {
84        let rel = self.env.catalog().and_then(|c| c.relation(view));
85        let invoker = rel.is_some_and(|rel| {
86            rel.kind == RelKind::View && option_on(&rel.options, "security_invoker")
87        });
88        let owner = match rel {
89            Some(rel) if !invoker => rel.owner,
90            _ => 0,
91        };
92        shift(query, self.perms.len());
93        self.funcs.append(&mut query.funcs);
94        for mut perm in perms {
95            if perm.check_as == 0 && !perm.invoker {
96                perm.check_as = owner;
97                perm.invoker = invoker;
98            }
99            self.perms.push(perm);
100        }
101    }
102
103    /// The functions that a stored expression of the table calls, such as a default, a generation expression or a check constraint. The executor checks `EXECUTE` on them as on the calls of the statement.
104    pub(crate) fn note_funcs(&mut self, expr: &Expr) {
105        expr.find(0, &mut |e, _| {
106            if let ExprKind::Func(f) = &e.kind {
107                self.funcs.push(f.oid);
108            }
109            None::<()>
110        });
111    }
112
113    /// `markVarForSelectPriv`: each column of a relation that the expression reads needs `SELECT`, in the privileges of the relation at the level of the column.
114    pub(crate) fn mark_select(&mut self, expr: &Expr) {
115        let mut vars = Vec::new();
116        expr.find(0, &mut |e, depth| {
117            if let ExprKind::Var(var) = &e.kind
118                && var.levels_up >= depth
119            {
120                vars.push((var.levels_up - depth, var.relation, var.attnum));
121            }
122            None::<()>
123        });
124        for (level, relation, attnum) in vars {
125            if let Some(perm) = self.relation_perm(level, relation) {
126                self.perms[perm].required |= ACL_SELECT;
127                self.perms[perm].selected.insert(attnum);
128            }
129        }
130    }
131
132    /// The part of `markRTEForSelectPriv` for a relation of the scope `level` with no column, as for `t.*` of a table with no columns.
133    pub(crate) fn mark_relation(&mut self, level: usize, relation: usize) {
134        if let Some(perm) = self.relation_perm(level, relation) {
135            self.perms[perm].required |= ACL_SELECT;
136        }
137    }
138
139    /// The index of the privileges of a relation of the scope `level`, or `None` for a relation that needs no privileges.
140    fn relation_perm(&self, level: usize, relation: usize) -> Option<usize> {
141        let (_, scope) = self.levels().nth(level)?;
142        scope.relations.get(relation)?.perm
143    }
144
145    /// The privileges of the target table of the write, which `target_table` made, or `None`.
146    pub(crate) fn target_perm_mut(&mut self) -> Option<&mut PermInfo> {
147        self.target_perm.and_then(|perm| self.perms.get_mut(perm))
148    }
149
150    /// `insertedCols`: the columns of the target table that an `INSERT` gives values.
151    pub(crate) fn mark_inserted(&mut self, given: &[Given]) {
152        if let Some(perm) = self.target_perm_mut() {
153            perm.inserted
154                .extend(given.iter().map(|(i, _, _)| i16::try_from(i + 1).unwrap_or(i16::MAX)));
155        }
156    }
157
158    /// `updatedCols`: the columns of the target table that `SET` gives, which need `UPDATE`.
159    pub(crate) fn mark_updated(&mut self, columns: &[i16]) {
160        if let Some(perm) = self.target_perm_mut() {
161            perm.required |= ACL_UPDATE;
162            perm.updated.extend(columns.iter().copied());
163        }
164    }
165
166    /// The part of `transformLockingClause` for the privileges: a relation with a lock on its rows needs `UPDATE`, also in a subquery of `FROM` and in the query of a view.
167    pub(crate) fn mark_locked(&mut self, query: &Query) {
168        for mark in &query.row_marks {
169            if let Some(perm) = query.relations.get(mark.relation).and_then(|r| r.perm) {
170                self.perms[perm].required |= ACL_UPDATE;
171            }
172        }
173        for relation in &query.relations {
174            if let Some(sub) = relation.subquery.as_deref() {
175                self.mark_locked(sub);
176            }
177        }
178    }
179}