uqa_sql/catalog/security/
columns.rs1use std::collections::{BTreeMap, BTreeSet};
10
11use super::{TableAclEntry, TablePrivileges};
12use crate::ast::RoleAttribute;
13use crate::SQLError;
14
15use super::table::{acl_grantor, grant_option_roles, TableAclPrivilege, TablePrivilegeCheck};
16use super::TableSecurity;
17use crate::catalog::roles::{role_inherits, RoleDefinition, RoleMembership, RoleMembershipKey};
18
19pub fn column_grant_option_roles(
20 security: &TableSecurity,
21 column: &str,
22 privilege: TableAclPrivilege,
23) -> BTreeSet<String> {
24 let mut reachable = grant_option_roles(security, privilege);
25 let Some(acl) = security.column_acls.get(column) else {
26 return reachable;
27 };
28 loop {
29 let mut changed = false;
30 for entry in acl {
31 if entry.role != "PUBLIC"
32 && entry.grant_options.intersects(privilege.mask())
33 && reachable.contains(acl_grantor(entry, &security.role_owner))
34 {
35 changed |= reachable.insert(entry.role.clone());
36 }
37 }
38 if !changed {
39 return reachable;
40 }
41 }
42}
43
44pub fn select_column_acl_grantor(
45 security: &TableSecurity,
46 column: &str,
47 privilege: TableAclPrivilege,
48 current_user: &str,
49 roles: &BTreeMap<String, RoleDefinition>,
50 memberships: &BTreeMap<RoleMembershipKey, RoleMembership>,
51) -> Option<String> {
52 if role_inherits(roles, memberships, current_user, &security.role_owner) {
53 return Some(security.role_owner.clone());
54 }
55 let grant_options = column_grant_option_roles(security, column, privilege);
56 if grant_options.contains(current_user) {
57 return Some(current_user.to_string());
58 }
59 grant_options
60 .into_iter()
61 .filter(|role| role != "PUBLIC" && role != &security.role_owner)
62 .find(|role| role_inherits(roles, memberships, current_user, role))
63}
64
65pub fn role_has_column_privilege(
66 security: &TableSecurity,
67 column: &str,
68 subject: &str,
69 check: TablePrivilegeCheck,
70 roles: &BTreeMap<String, RoleDefinition>,
71 memberships: &BTreeMap<RoleMembershipKey, RoleMembership>,
72) -> bool {
73 if roles
74 .get(subject)
75 .is_some_and(|role| role.has(RoleAttribute::Superuser))
76 || role_inherits(roles, memberships, subject, &security.role_owner)
77 {
78 return true;
79 }
80 if check.grant_option {
81 return column_grant_option_roles(security, column, check.privilege)
82 .iter()
83 .any(|role| role_inherits(roles, memberships, subject, role));
84 }
85 if super::table::role_has_privilege(security, subject, check, roles, memberships) {
86 return true;
87 }
88 security.column_acls.get(column).is_some_and(|acl| {
89 acl.iter().any(|entry| {
90 entry.privileges.intersects(check.privilege.mask())
91 && (entry.role == "PUBLIC"
92 || role_inherits(roles, memberships, subject, &entry.role))
93 })
94 })
95}
96
97pub fn grant_column_acl(
98 security: &mut TableSecurity,
99 column: &str,
100 privilege: TableAclPrivilege,
101 grantees: &[String],
102 grantor: &str,
103 grant_option: bool,
104) {
105 let owner = security.role_owner.clone();
106 let acl = security.column_acls.entry(column.to_string()).or_default();
107 for grantee in grantees {
108 let position = acl
109 .iter()
110 .position(|entry| entry.role == *grantee && acl_grantor(entry, &owner) == grantor)
111 .unwrap_or_else(|| {
112 acl.push(TableAclEntry {
113 role: grantee.clone(),
114 grantor: Some(grantor.to_string()),
115 privileges: TablePrivileges::default(),
116 grant_options: TablePrivileges::default(),
117 });
118 acl.len() - 1
119 });
120 let entry = &mut acl[position];
121 entry.privileges.insert(privilege.mask());
122 if grant_option && grantee != "PUBLIC" && grantee != &owner {
123 entry.grant_options.insert(privilege.mask());
124 }
125 }
126}
127
128pub fn revoke_column_acl(
129 security: &mut TableSecurity,
130 column: &str,
131 privilege: TableAclPrivilege,
132 grantees: &[String],
133 grantor: &str,
134 grant_option_only: bool,
135 cascade: bool,
136) -> Result<(), SQLError> {
137 let before = column_grant_option_roles(security, column, privilege);
138 let owner = security.role_owner.clone();
139 let Some(acl) = security.column_acls.get_mut(column) else {
140 return Ok(());
141 };
142 for entry in acl
143 .iter_mut()
144 .filter(|entry| grantees.contains(&entry.role) && acl_grantor(entry, &owner) == grantor)
145 {
146 entry.grant_options.remove(privilege.mask());
147 if !grant_option_only {
148 entry.privileges.remove(privilege.mask());
149 }
150 }
151 remove_empty_entries(acl);
152 revoke_dependent_column_acl(security, column, privilege, &before, cascade)
153}
154
155fn revoke_dependent_column_acl(
156 security: &mut TableSecurity,
157 column: &str,
158 privilege: TableAclPrivilege,
159 before: &BTreeSet<String>,
160 cascade: bool,
161) -> Result<(), SQLError> {
162 loop {
163 let current = column_grant_option_roles(security, column, privilege);
164 let lost = before
165 .difference(¤t)
166 .cloned()
167 .collect::<BTreeSet<_>>();
168 if lost.is_empty() {
169 return Ok(());
170 }
171 let owner = security.role_owner.clone();
172 let dependent = security.column_acls.get(column).is_some_and(|acl| {
173 acl.iter().any(|entry| {
174 lost.contains(acl_grantor(entry, &owner))
175 && (entry.privileges.intersects(privilege.mask())
176 || entry.grant_options.intersects(privilege.mask()))
177 })
178 });
179 if !dependent {
180 return Ok(());
181 }
182 if !cascade {
183 return Err(SQLError::Routine {
184 sqlstate: "2BP01".into(),
185 message: "dependent privileges exist".into(),
186 });
187 }
188 let acl = security
189 .column_acls
190 .get_mut(column)
191 .expect("dependent column privileges require an explicit ACL");
192 for entry in acl
193 .iter_mut()
194 .filter(|entry| lost.contains(acl_grantor(entry, &owner)))
195 {
196 entry.privileges.remove(privilege.mask());
197 entry.grant_options.remove(privilege.mask());
198 }
199 remove_empty_entries(acl);
200 }
201}
202
203fn remove_empty_entries(acl: &mut Vec<TableAclEntry>) {
204 acl.retain(|entry| !entry.privileges.is_empty() || !entry.grant_options.is_empty());
205}