1use crate::ast::ObjectAclEntry;
10use crate::catalog::roles::{
11 identity::RoleSubject, role_inherits, RoleDefinition, RoleMembership, RoleMembershipKey,
12 RoleReference,
13};
14use crate::SQLError;
15use std::collections::{BTreeMap, BTreeSet};
16use uqa_core::{catalog_acl::AclGrantee, catalog_role::RoleIdentity};
17
18pub fn privilege_allowed(
20 owner: &RoleIdentity,
21 acl: Option<&[ObjectAclEntry]>,
22 grant_option: bool,
23 superuser: bool,
24 has_role: impl Fn(&RoleIdentity) -> bool,
25) -> bool {
26 if superuser || (grant_option && has_role(owner)) {
27 return true;
28 }
29 acl.map_or(!grant_option, |acl| {
30 acl.iter().any(|entry| {
31 (!grant_option || entry.grant_option)
32 && ((entry.role.is_none() && !grant_option)
33 || entry.role.as_ref().is_some_and(&has_role))
34 })
35 })
36}
37
38pub fn grant_option_roles(
40 owner: RoleIdentity,
41 acl: Option<&[ObjectAclEntry]>,
42) -> BTreeSet<RoleIdentity> {
43 let mut reachable = BTreeSet::from([owner]);
44 let Some(acl) = acl else {
45 return reachable;
46 };
47 loop {
48 let mut changed = false;
49 for entry in acl {
50 if let Some(role) = entry.role {
51 if entry.grant_option && reachable.contains(&entry.grantor) {
52 changed |= reachable.insert(role);
53 }
54 }
55 }
56 if !changed {
57 return reachable;
58 }
59 }
60}
61
62pub fn select_grantor(
64 owner: RoleIdentity,
65 acl: Option<&[ObjectAclEntry]>,
66 current_user: &(impl RoleSubject + ?Sized),
67 roles: &BTreeMap<String, RoleDefinition>,
68 memberships: &BTreeMap<RoleMembershipKey, RoleMembership>,
69) -> Option<RoleIdentity> {
70 if role_inherits(roles, memberships, current_user, &owner) {
71 return Some(owner);
72 }
73 let grant_options = grant_option_roles(owner, acl);
74 if let Some(identity) = current_user
75 .role_definition(roles)
76 .map(RoleDefinition::identity)
77 .filter(|identity| grant_options.contains(identity))
78 {
79 return Some(identity);
80 }
81 acl.and_then(|acl| {
82 acl.iter()
83 .filter_map(|entry| entry.role)
84 .filter(|role| grant_options.contains(role))
85 .find(|role| role_inherits(roles, memberships, current_user, role))
86 })
87}
88
89fn materialize(
90 owner: RoleIdentity,
91 acl: &mut Option<Vec<ObjectAclEntry>>,
92) -> &mut Vec<ObjectAclEntry> {
93 acl.get_or_insert_with(|| {
94 vec![
95 ObjectAclEntry {
96 role: None,
97 grantor: owner,
98 grant_option: false,
99 },
100 ObjectAclEntry {
101 role: Some(owner),
102 grantor: owner,
103 grant_option: false,
104 },
105 ]
106 })
107}
108
109#[must_use]
111pub fn explicit_acl(
112 owner: RoleIdentity,
113 mut acl: Option<Vec<ObjectAclEntry>>,
114) -> Option<Vec<ObjectAclEntry>> {
115 materialize(owner, &mut acl);
116 acl
117}
118
119pub fn grant(
121 owner: RoleIdentity,
122 acl: &mut Option<Vec<ObjectAclEntry>>,
123 grantee: Option<RoleIdentity>,
124 grantor: RoleIdentity,
125 grant_option: bool,
126) {
127 let entries = materialize(owner, acl);
128 if let Some(entry) = entries
129 .iter_mut()
130 .find(|entry| entry.role == grantee && entry.grantor == grantor)
131 {
132 entry.grant_option |= grant_option;
133 } else {
134 entries.push(ObjectAclEntry {
135 role: grantee,
136 grantor,
137 grant_option,
138 });
139 }
140}
141
142pub fn revoke(
144 owner: RoleIdentity,
145 acl: &mut Option<Vec<ObjectAclEntry>>,
146 grantee: Option<RoleIdentity>,
147 grantor: RoleIdentity,
148 grant_option_only: bool,
149 cascade: bool,
150) -> Result<(), SQLError> {
151 let before = grant_option_roles(owner, acl.as_deref());
152 let entries = materialize(owner, acl);
153 let Some(position) = entries
154 .iter()
155 .position(|entry| entry.role == grantee && entry.grantor == grantor)
156 else {
157 return Ok(());
158 };
159 if grant_option_only {
160 if !entries[position].grant_option {
161 return Ok(());
162 }
163 entries[position].grant_option = false;
164 } else {
165 entries.remove(position);
166 }
167 revoke_dependents(owner, acl, &before, cascade)
168}
169
170fn revoke_dependents(
171 owner: RoleIdentity,
172 acl: &mut Option<Vec<ObjectAclEntry>>,
173 before: &BTreeSet<RoleIdentity>,
174 cascade: bool,
175) -> Result<(), SQLError> {
176 loop {
177 let current = grant_option_roles(owner, acl.as_deref());
178 let lost = before
179 .difference(¤t)
180 .copied()
181 .collect::<BTreeSet<_>>();
182 let Some(entries) = acl.as_mut() else {
183 return Ok(());
184 };
185 if lost.is_empty() || !entries.iter().any(|entry| lost.contains(&entry.grantor)) {
186 return Ok(());
187 }
188 if !cascade {
189 return Err(SQLError::Diagnostic {
190 sqlstate: "2BP01".into(),
191 message: "dependent privileges exist".into(),
192 detail: None,
193 hint: Some("Use CASCADE to revoke them too.".into()),
194 });
195 }
196 entries.retain(|entry| !lost.contains(&entry.grantor));
197 }
198}
199
200pub fn rewrite_owner(
202 acl: &mut Option<Vec<ObjectAclEntry>>,
203 old_owner: RoleIdentity,
204 new_owner: RoleIdentity,
205) {
206 let Some(entries) = acl.as_mut() else {
207 return;
208 };
209 for entry in entries.iter_mut() {
210 if entry.role == Some(old_owner) {
211 entry.role = Some(new_owner);
212 }
213 if entry.grantor == old_owner {
214 entry.grantor = new_owner;
215 }
216 }
217 let mut merged: Vec<ObjectAclEntry> = Vec::with_capacity(entries.len());
218 for entry in std::mem::take(entries) {
219 if let Some(existing) = merged
220 .iter_mut()
221 .find(|existing| existing.role == entry.role && existing.grantor == entry.grantor)
222 {
223 existing.grant_option |= entry.grant_option;
224 } else {
225 merged.push(entry);
226 }
227 }
228 *entries = merged;
229}
230
231pub fn validate_acl(
233 owner: RoleIdentity,
234 acl: Option<&[ObjectAclEntry]>,
235 object_kind: &str,
236) -> Result<(), SQLError> {
237 let invalid =
238 |message: &str| SQLError::Internal(format!("invalid {object_kind} authority: {message}"));
239 if !owner.is_valid() {
240 return Err(invalid("missing owner incarnation"));
241 }
242 let reachable = grant_option_roles(owner, acl);
243 let mut paths = BTreeSet::new();
244 for entry in acl.into_iter().flatten() {
245 if !entry.grantor.is_valid() || entry.role.is_some_and(|role| !role.is_valid()) {
246 return Err(invalid("missing ACL endpoint incarnation"));
247 }
248 if entry.role.is_none() && entry.grant_option {
249 return Err(invalid("PUBLIC cannot retain a grant option"));
250 }
251 if !paths.insert((entry.role, entry.grantor)) {
252 return Err(invalid("duplicate ACL grant path"));
253 }
254 if !reachable.contains(&entry.grantor) {
255 return Err(invalid("ACL grantor has no owner-rooted grant option"));
256 }
257 }
258 Ok(())
259}
260
261pub fn acl_roles(owner: RoleIdentity, acl: Option<&[ObjectAclEntry]>) -> BTreeSet<RoleIdentity> {
263 let mut identities = BTreeSet::from([owner]);
264 for entry in acl.into_iter().flatten() {
265 identities.extend(entry.role);
266 identities.insert(entry.grantor);
267 }
268 identities
269}
270
271pub fn added_acl_roles(
273 before: (RoleIdentity, Option<&[ObjectAclEntry]>),
274 after: (RoleIdentity, Option<&[ObjectAclEntry]>),
275 roles: &BTreeMap<String, RoleDefinition>,
276 object_kind: &str,
277 added: &mut BTreeSet<String>,
278) -> Result<(), SQLError> {
279 let mut old = acl_roles(before.0, before.1);
280 old.remove(&before.0);
281 let mut new = acl_roles(after.0, after.1);
282 new.remove(&after.0);
283 validate_acl(after.0, after.1, object_kind)?;
284 for identity in new.difference(&old) {
285 added.insert(
286 identity
287 .role_name(roles)
288 .ok_or_else(|| {
289 SQLError::Internal(format!(
290 "invalid {object_kind} authority: missing ACL incarnation"
291 ))
292 })?
293 .to_owned(),
294 );
295 }
296 Ok(())
297}
298
299pub fn bind_grantees(
301 grantees: &[AclGrantee],
302 roles: &BTreeMap<String, RoleDefinition>,
303) -> Result<Vec<Option<RoleIdentity>>, SQLError> {
304 grantees
305 .iter()
306 .map(|grantee| {
307 grantee
308 .role_name()
309 .map(|name| {
310 RoleReference::from(name)
311 .bind(roles)
312 .map(|role| role.identity())
313 })
314 .transpose()
315 })
316 .collect()
317}
318
319pub fn acl_warning(is_grant: bool, local_name: &str) -> crate::SQLNotice {
321 super::acl_warning::acl_warning(is_grant, false, local_name)
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327
328 fn role(value: u8) -> RoleIdentity {
329 RoleIdentity {
330 oid: i64::from(value),
331 object_id: [value; 16],
332 }
333 }
334
335 fn grant_entry(grantee: u8, grantor: u8) -> ObjectAclEntry {
336 ObjectAclEntry {
337 role: Some(role(grantee)),
338 grantor: role(grantor),
339 grant_option: true,
340 }
341 }
342
343 #[test]
344 fn grant_option_reachability_requires_an_owner_root() {
345 let disconnected_cycle = [grant_entry(2, 3), grant_entry(3, 2)];
346 assert_eq!(
347 grant_option_roles(role(1), Some(&disconnected_cycle)),
348 BTreeSet::from([role(1)])
349 );
350 let rooted_cycle = [grant_entry(2, 1), grant_entry(3, 2), grant_entry(2, 3)];
351 assert_eq!(
352 grant_option_roles(role(1), Some(&rooted_cycle)),
353 BTreeSet::from([role(1), role(2), role(3)])
354 );
355 }
356
357 #[test]
358 fn grant_option_reachability_accepts_an_independent_owner_path() {
359 let acl = [
360 grant_entry(2, 1),
361 grant_entry(3, 2),
362 grant_entry(3, 1),
363 grant_entry(4, 3),
364 ];
365 assert_eq!(
366 grant_option_roles(role(1), Some(&acl[2..])),
367 BTreeSet::from([role(1), role(3), role(4)])
368 );
369 }
370
371 #[test]
372 fn revoking_a_grant_option_with_dependent_grants_requires_cascade() {
373 let owner = role(1);
374 let mut acl = None;
375 grant(owner, &mut acl, Some(role(2)), owner, true);
376 grant(owner, &mut acl, Some(role(3)), role(2), false);
377 let error = revoke(owner, &mut acl.clone(), Some(role(2)), owner, true, false).unwrap_err();
378 assert_eq!(error.sqlstate(), Some("2BP01"));
379 assert_eq!(error.hint(), Some("Use CASCADE to revoke them too."));
380 revoke(owner, &mut acl, Some(role(2)), owner, true, true).unwrap();
381 assert_eq!(
382 acl.unwrap(),
383 [
384 ObjectAclEntry {
385 role: None,
386 grantor: owner,
387 grant_option: false
388 },
389 ObjectAclEntry {
390 role: Some(owner),
391 grantor: owner,
392 grant_option: false
393 },
394 ObjectAclEntry {
395 role: Some(role(2)),
396 grantor: owner,
397 grant_option: false
398 },
399 ]
400 );
401 }
402}