1use crate::catalog::roles::identity::RoleSubject;
10use std::collections::{BTreeMap, BTreeSet};
11use uqa_core::catalog_acl::AclGrantee;
12
13use crate::ast::{RoleAttribute, SequencePrivilege};
14use crate::SQLError;
15use uqa_core::catalog_sequence::{SequenceAclEntry, SequencePrivileges};
16
17use super::SequenceSecurity;
18use crate::catalog::roles::{role_inherits, RoleDefinition, RoleMembership, RoleMembershipKey};
19
20mod invariants;
21pub use invariants::validate_sequence_security_invariants;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
24pub enum AclPrivilege {
25 Select,
26 Update,
27 Usage,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub struct PrivilegeCheck {
32 pub privilege: AclPrivilege,
33 pub grant_option: bool,
34}
35
36impl AclPrivilege {
37 pub const fn mask(self) -> SequencePrivileges {
38 match self {
39 Self::Select => SequencePrivileges {
40 select: true,
41 update: false,
42 usage: false,
43 },
44 Self::Update => SequencePrivileges {
45 select: false,
46 update: true,
47 usage: false,
48 },
49 Self::Usage => SequencePrivileges {
50 select: false,
51 update: false,
52 usage: true,
53 },
54 }
55 }
56}
57
58pub fn requested_acl_privileges(
59 requested: &[SequencePrivilege],
60) -> Result<Vec<AclPrivilege>, SQLError> {
61 requested
62 .iter()
63 .map(|privilege| match privilege {
64 SequencePrivilege::Select => Ok(AclPrivilege::Select),
65 SequencePrivilege::Update => Ok(AclPrivilege::Update),
66 SequencePrivilege::Usage => Ok(AclPrivilege::Usage),
67 SequencePrivilege::ColumnsUnsupported => Err(SQLError::Routine {
68 sqlstate: "0LP01".into(),
69 message: "column privileges are only valid for relations".into(),
70 }),
71 SequencePrivilege::Unsupported(name) => Err(SQLError::Routine {
72 sqlstate: "0LP01".into(),
73 message: format!("invalid privilege type {name} for sequence"),
74 }),
75 })
76 .collect()
77}
78
79pub fn parse_privilege_checks(value: &str) -> Result<Vec<PrivilegeCheck>, SQLError> {
80 value
81 .split(',')
82 .map(|item| {
83 let item = item.trim();
84 let upper = item.to_ascii_uppercase();
85 let (name, grant_option) = upper
86 .strip_suffix(" WITH GRANT OPTION")
87 .map_or((upper.as_str(), false), |name| (name.trim_end(), true));
88 let privilege = match name {
89 "SELECT" => AclPrivilege::Select,
90 "UPDATE" => AclPrivilege::Update,
91 "USAGE" => AclPrivilege::Usage,
92 _ => {
93 return Err(SQLError::Routine {
94 sqlstate: "22023".into(),
95 message: format!("unrecognized privilege type: \"{item}\""),
96 })
97 }
98 };
99 Ok(PrivilegeCheck {
100 privilege,
101 grant_option,
102 })
103 })
104 .collect()
105}
106
107fn acl_grantor<'a>(entry: &'a SequenceAclEntry, owner: &'a str) -> &'a str {
108 entry.grantor.as_deref().unwrap_or(owner)
109}
110
111fn materialize_acl(security: &mut SequenceSecurity) {
112 if security.acl.is_none() {
113 security.acl = Some(vec![SequenceAclEntry {
114 role: security.role_owner.clone().into(),
115 grantor: Some(security.role_owner.clone()),
116 privileges: SequencePrivileges::ALL,
117 grant_options: SequencePrivileges::default(),
118 }]);
119 }
120}
121
122fn grant_option_roles(security: &SequenceSecurity, privilege: AclPrivilege) -> BTreeSet<String> {
123 let mut reachable = BTreeSet::from([security.role_owner.clone()]);
124 let Some(acl) = security.acl.as_ref() else {
125 return reachable;
126 };
127 loop {
128 let mut changed = false;
129 for entry in acl {
130 let Some(role) = entry.role.role_name() else {
131 continue;
132 };
133 if entry.grant_options.intersects(privilege.mask())
134 && reachable.contains(acl_grantor(entry, &security.role_owner))
135 {
136 changed |= reachable.insert(role.to_owned());
137 }
138 }
139 if !changed {
140 return reachable;
141 }
142 }
143}
144
145pub fn select_acl_grantor(
146 security: &SequenceSecurity,
147 privilege: AclPrivilege,
148 current_user: &(impl RoleSubject + ?Sized),
149 roles: &BTreeMap<String, RoleDefinition>,
150 memberships: &BTreeMap<RoleMembershipKey, RoleMembership>,
151) -> Option<String> {
152 let current_user = current_user.role_name(roles)?;
153 if role_inherits(roles, memberships, current_user, &security.role_owner) {
154 return Some(security.role_owner.clone());
155 }
156 let grant_options = grant_option_roles(security, privilege);
157 if grant_options.contains(current_user) {
158 return Some(current_user.to_string());
159 }
160 security.acl.as_ref().and_then(|acl| {
161 acl.iter()
162 .filter_map(|entry| entry.role.role_name())
163 .filter(|role| grant_options.contains(*role))
164 .find(|role| role_inherits(roles, memberships, current_user, *role))
165 .map(str::to_owned)
166 })
167}
168
169pub fn role_has_privilege(
170 security: &SequenceSecurity,
171 subject: &(impl RoleSubject + ?Sized),
172 check: PrivilegeCheck,
173 roles: &BTreeMap<String, RoleDefinition>,
174 memberships: &BTreeMap<RoleMembershipKey, RoleMembership>,
175) -> bool {
176 if subject
177 .role_definition(roles)
178 .is_some_and(|role| role.has(RoleAttribute::Superuser))
179 {
180 return true;
181 }
182 if check.grant_option {
183 return grant_option_roles(security, check.privilege)
184 .iter()
185 .any(|role| role_inherits(roles, memberships, subject, role));
186 }
187 match security.acl.as_ref() {
188 None => role_inherits(roles, memberships, subject, &security.role_owner),
189 Some(acl) => acl.iter().any(|entry| {
190 entry.privileges.intersects(check.privilege.mask())
191 && (entry.role.is_public()
192 || role_inherits(roles, memberships, subject, &entry.role))
193 }),
194 }
195}
196
197pub fn grant_acl(
198 security: &mut SequenceSecurity,
199 privilege: AclPrivilege,
200 grantees: &[AclGrantee],
201 grantor: &str,
202 grant_option: bool,
203) {
204 materialize_acl(security);
205 let owner = security.role_owner.clone();
206 let acl = security
207 .acl
208 .as_mut()
209 .expect("sequence ACL was materialized");
210 for grantee in grantees {
211 let position = acl
212 .iter()
213 .position(|entry| entry.role == *grantee && acl_grantor(entry, &owner) == grantor)
214 .unwrap_or_else(|| {
215 acl.push(SequenceAclEntry {
216 role: grantee.clone(),
217 grantor: Some(grantor.to_string()),
218 privileges: SequencePrivileges::default(),
219 grant_options: SequencePrivileges::default(),
220 });
221 acl.len() - 1
222 });
223 let entry = &mut acl[position];
224 entry.privileges.insert(privilege.mask());
225 if grant_option && grantee.role_name().is_some_and(|name| name != owner) {
226 entry.grant_options.insert(privilege.mask());
227 }
228 }
229}
230
231pub fn revoke_acl(
232 security: &mut SequenceSecurity,
233 privilege: AclPrivilege,
234 grantees: &[AclGrantee],
235 grantor: &str,
236 grant_option_only: bool,
237 cascade: bool,
238) -> Result<(), SQLError> {
239 let before = grant_option_roles(security, privilege);
240 materialize_acl(security);
241 let owner = security.role_owner.clone();
242 let acl = security
243 .acl
244 .as_mut()
245 .expect("sequence ACL was materialized");
246 for entry in acl
247 .iter_mut()
248 .filter(|entry| grantees.contains(&entry.role) && acl_grantor(entry, &owner) == grantor)
249 {
250 entry.grant_options.remove(privilege.mask());
251 if !grant_option_only {
252 entry.privileges.remove(privilege.mask());
253 }
254 }
255 remove_empty_entries(acl);
256 revoke_dependent_acl(security, privilege, &before, cascade)
257}
258
259fn revoke_dependent_acl(
260 security: &mut SequenceSecurity,
261 privilege: AclPrivilege,
262 before: &BTreeSet<String>,
263 cascade: bool,
264) -> Result<(), SQLError> {
265 loop {
266 let current = grant_option_roles(security, privilege);
267 let lost = before
268 .difference(¤t)
269 .cloned()
270 .collect::<BTreeSet<_>>();
271 if lost.is_empty() {
272 return Ok(());
273 }
274 let owner = security.role_owner.clone();
275 let dependent = security.acl.as_ref().is_some_and(|acl| {
276 acl.iter().any(|entry| {
277 lost.contains(acl_grantor(entry, &owner))
278 && (entry.privileges.intersects(privilege.mask())
279 || entry.grant_options.intersects(privilege.mask()))
280 })
281 });
282 if !dependent {
283 return Ok(());
284 }
285 if !cascade {
286 return Err(SQLError::Routine {
287 sqlstate: "2BP01".into(),
288 message: "dependent privileges exist".into(),
289 });
290 }
291 let acl = security
292 .acl
293 .as_mut()
294 .expect("dependent sequence privileges require an explicit ACL");
295 for entry in acl
296 .iter_mut()
297 .filter(|entry| lost.contains(acl_grantor(entry, &owner)))
298 {
299 entry.privileges.remove(privilege.mask());
300 entry.grant_options.remove(privilege.mask());
301 }
302 remove_empty_entries(acl);
303 }
304}
305
306fn remove_empty_entries(acl: &mut Vec<SequenceAclEntry>) {
307 acl.retain(|entry| !entry.privileges.is_empty() || !entry.grant_options.is_empty());
308}
309
310pub fn rewrite_acl_owner(security: &mut SequenceSecurity, new_owner: &str) {
311 let old_owner = std::mem::replace(&mut security.role_owner, new_owner.to_string());
312 let Some(acl) = security.acl.as_mut() else {
313 return;
314 };
315 for entry in acl.iter_mut() {
316 if entry.role.role_name() == Some(old_owner.as_str()) {
317 entry.role = new_owner.into();
318 }
319 if entry.grantor.as_deref() == Some(&old_owner) {
320 entry.grantor = Some(new_owner.to_string());
321 }
322 }
323 let mut merged: Vec<SequenceAclEntry> = Vec::with_capacity(acl.len());
324 for entry in std::mem::take(acl) {
325 if let Some(existing) = merged.iter_mut().find(|existing| {
326 existing.role == entry.role
327 && acl_grantor(existing, new_owner) == acl_grantor(&entry, new_owner)
328 }) {
329 existing.privileges.insert(entry.privileges);
330 existing.grant_options.insert(entry.grant_options);
331 } else {
332 merged.push(entry);
333 }
334 }
335 *acl = merged;
336}
337
338pub fn role_can_view_sequence(
339 security: &SequenceSecurity,
340 subject: &(impl RoleSubject + ?Sized),
341 roles: &BTreeMap<String, RoleDefinition>,
342 memberships: &BTreeMap<RoleMembershipKey, RoleMembership>,
343) -> bool {
344 role_inherits(roles, memberships, subject, &security.role_owner)
345 || role_has_any_sequence_privilege(security, subject, roles, memberships)
346}
347
348pub fn role_has_any_sequence_privilege(
349 security: &SequenceSecurity,
350 subject: &(impl RoleSubject + ?Sized),
351 roles: &BTreeMap<String, RoleDefinition>,
352 memberships: &BTreeMap<RoleMembershipKey, RoleMembership>,
353) -> bool {
354 [
355 AclPrivilege::Select,
356 AclPrivilege::Update,
357 AclPrivilege::Usage,
358 ]
359 .into_iter()
360 .any(|privilege| {
361 role_has_privilege(
362 security,
363 subject,
364 PrivilegeCheck {
365 privilege,
366 grant_option: false,
367 },
368 roles,
369 memberships,
370 )
371 })
372}
373
374pub fn role_can_select_sequence(
375 security: &SequenceSecurity,
376 subject: &(impl RoleSubject + ?Sized),
377 roles: &BTreeMap<String, RoleDefinition>,
378 memberships: &BTreeMap<RoleMembershipKey, RoleMembership>,
379) -> bool {
380 role_has_privilege(
381 security,
382 subject,
383 PrivilegeCheck {
384 privilege: AclPrivilege::Select,
385 grant_option: false,
386 },
387 roles,
388 memberships,
389 )
390}
391
392pub fn role_can_read_sequence_value(
393 security: &SequenceSecurity,
394 subject: &(impl RoleSubject + ?Sized),
395 roles: &BTreeMap<String, RoleDefinition>,
396 memberships: &BTreeMap<RoleMembershipKey, RoleMembership>,
397) -> bool {
398 [AclPrivilege::Select, AclPrivilege::Usage]
399 .into_iter()
400 .any(|privilege| {
401 role_has_privilege(
402 security,
403 subject,
404 PrivilegeCheck {
405 privilege,
406 grant_option: false,
407 },
408 roles,
409 memberships,
410 )
411 })
412}