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