1use std::collections::{BTreeMap, BTreeSet};
10
11use super::{TableAclEntry, TablePrivileges};
12use crate::ast::{RoleAttribute, TablePrivilege, TablePrivilegeSpec};
13use crate::SQLError;
14
15use super::TableSecurity;
16use crate::catalog::roles::{role_inherits, RoleDefinition, RoleMembership, RoleMembershipKey};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
19pub enum TableAclPrivilege {
20 Select,
21 Insert,
22 Update,
23 Delete,
24 Truncate,
25 References,
26 Trigger,
27 Maintain,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub struct TablePrivilegeCheck {
32 pub privilege: TableAclPrivilege,
33 pub grant_option: bool,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct RequestedTablePrivileges {
38 pub table: Vec<TableAclPrivilege>,
39 pub columns: Vec<(TableAclPrivilege, String)>,
40}
41
42impl TableAclPrivilege {
43 pub const ALL: [Self; 8] = [
44 Self::Select,
45 Self::Insert,
46 Self::Update,
47 Self::Delete,
48 Self::Truncate,
49 Self::References,
50 Self::Trigger,
51 Self::Maintain,
52 ];
53
54 pub const COLUMN_ALL: [Self; 4] = [Self::Select, Self::Insert, Self::Update, Self::References];
55
56 pub const fn mask(self) -> TablePrivileges {
57 let mut privileges = TablePrivileges {
58 select: false,
59 insert: false,
60 update: false,
61 delete: false,
62 truncate: false,
63 references: false,
64 trigger: false,
65 maintain: false,
66 };
67 match self {
68 Self::Select => privileges.select = true,
69 Self::Insert => privileges.insert = true,
70 Self::Update => privileges.update = true,
71 Self::Delete => privileges.delete = true,
72 Self::Truncate => privileges.truncate = true,
73 Self::References => privileges.references = true,
74 Self::Trigger => privileges.trigger = true,
75 Self::Maintain => privileges.maintain = true,
76 }
77 privileges
78 }
79}
80
81pub fn requested_acl_privileges(
82 requested: &[TablePrivilegeSpec],
83) -> Result<RequestedTablePrivileges, SQLError> {
84 if requested.is_empty() {
85 return Ok(RequestedTablePrivileges {
86 table: TableAclPrivilege::ALL.into(),
87 columns: Vec::new(),
88 });
89 }
90 let mut table = Vec::with_capacity(requested.len());
91 let mut columns = Vec::new();
92 for spec in requested {
93 let privilege = match &spec.privilege {
94 TablePrivilege::Select => TableAclPrivilege::Select,
95 TablePrivilege::Insert => TableAclPrivilege::Insert,
96 TablePrivilege::Update => TableAclPrivilege::Update,
97 TablePrivilege::Delete => TableAclPrivilege::Delete,
98 TablePrivilege::Truncate => TableAclPrivilege::Truncate,
99 TablePrivilege::References => TableAclPrivilege::References,
100 TablePrivilege::Trigger => TableAclPrivilege::Trigger,
101 TablePrivilege::Maintain => TableAclPrivilege::Maintain,
102 TablePrivilege::Usage => {
103 return Err(SQLError::Routine {
104 sqlstate: "0LP01".into(),
105 message: if spec.columns.is_empty() {
106 "invalid privilege type USAGE for table".into()
107 } else {
108 "invalid privilege type USAGE for column".into()
109 },
110 })
111 }
112 TablePrivilege::Unsupported(name) => {
113 return Err(SQLError::Routine {
114 sqlstate: "0LP01".into(),
115 message: format!(
116 "invalid privilege type {name} for {}",
117 if spec.columns.is_empty() {
118 "table"
119 } else {
120 "column"
121 }
122 ),
123 })
124 }
125 };
126 if spec.columns.is_empty() {
127 if !table.contains(&privilege) {
128 table.push(privilege);
129 }
130 } else {
131 if !TableAclPrivilege::COLUMN_ALL.contains(&privilege) {
132 return Err(SQLError::Routine {
133 sqlstate: "0LP01".into(),
134 message: format!(
135 "invalid privilege type {} for column",
136 table_privilege_name(&spec.privilege)
137 ),
138 });
139 }
140 for column in &spec.columns {
141 let requested = (privilege, column.clone());
142 if !columns.contains(&requested) {
143 columns.push(requested);
144 }
145 }
146 }
147 }
148 Ok(RequestedTablePrivileges { table, columns })
149}
150
151fn table_privilege_name(privilege: &TablePrivilege) -> &str {
152 match privilege {
153 TablePrivilege::Select => "SELECT",
154 TablePrivilege::Insert => "INSERT",
155 TablePrivilege::Update => "UPDATE",
156 TablePrivilege::Delete => "DELETE",
157 TablePrivilege::Truncate => "TRUNCATE",
158 TablePrivilege::References => "REFERENCES",
159 TablePrivilege::Trigger => "TRIGGER",
160 TablePrivilege::Maintain => "MAINTAIN",
161 TablePrivilege::Usage => "USAGE",
162 TablePrivilege::Unsupported(name) => name,
163 }
164}
165
166pub fn parse_privilege_checks(value: &str) -> Result<Vec<TablePrivilegeCheck>, SQLError> {
167 value
168 .split(',')
169 .map(|item| {
170 let item = item.trim();
171 let upper = item.to_ascii_uppercase();
172 let (name, grant_option) = upper
173 .strip_suffix(" WITH GRANT OPTION")
174 .map_or((upper.as_str(), false), |name| (name.trim_end(), true));
175 let privilege = match name {
176 "SELECT" => TableAclPrivilege::Select,
177 "INSERT" => TableAclPrivilege::Insert,
178 "UPDATE" => TableAclPrivilege::Update,
179 "DELETE" => TableAclPrivilege::Delete,
180 "TRUNCATE" => TableAclPrivilege::Truncate,
181 "REFERENCES" => TableAclPrivilege::References,
182 "TRIGGER" => TableAclPrivilege::Trigger,
183 "MAINTAIN" => TableAclPrivilege::Maintain,
184 _ => {
185 return Err(SQLError::Routine {
186 sqlstate: "22023".into(),
187 message: format!("unrecognized privilege type: \"{item}\""),
188 })
189 }
190 };
191 Ok(TablePrivilegeCheck {
192 privilege,
193 grant_option,
194 })
195 })
196 .collect()
197}
198
199pub fn parse_column_privilege_checks(value: &str) -> Result<Vec<TablePrivilegeCheck>, SQLError> {
200 let checks = parse_privilege_checks(value)?;
201 if let Some(invalid) = checks
202 .iter()
203 .find(|check| !TableAclPrivilege::COLUMN_ALL.contains(&check.privilege))
204 {
205 let item = value
206 .split(',')
207 .find(|item| {
208 let upper = item.trim().to_ascii_uppercase();
209 let name = upper
210 .strip_suffix(" WITH GRANT OPTION")
211 .unwrap_or(&upper)
212 .trim_end();
213 table_privilege_name_from_acl(invalid.privilege) == name
214 })
215 .map_or(value, str::trim);
216 return Err(SQLError::Routine {
217 sqlstate: "22023".into(),
218 message: format!("unrecognized privilege type: \"{item}\""),
219 });
220 }
221 Ok(checks)
222}
223
224fn table_privilege_name_from_acl(privilege: TableAclPrivilege) -> &'static str {
225 match privilege {
226 TableAclPrivilege::Select => "SELECT",
227 TableAclPrivilege::Insert => "INSERT",
228 TableAclPrivilege::Update => "UPDATE",
229 TableAclPrivilege::Delete => "DELETE",
230 TableAclPrivilege::Truncate => "TRUNCATE",
231 TableAclPrivilege::References => "REFERENCES",
232 TableAclPrivilege::Trigger => "TRIGGER",
233 TableAclPrivilege::Maintain => "MAINTAIN",
234 }
235}
236
237pub fn acl_grantor<'a>(entry: &'a TableAclEntry, owner: &'a str) -> &'a str {
238 entry.grantor.as_deref().unwrap_or(owner)
239}
240
241fn materialize_acl(security: &mut TableSecurity) {
242 if security.acl.is_none() {
243 security.acl = Some(vec![TableAclEntry {
244 role: security.role_owner.clone(),
245 grantor: Some(security.role_owner.clone()),
246 privileges: TablePrivileges::ALL,
247 grant_options: TablePrivileges::default(),
248 }]);
249 }
250}
251
252pub fn grant_option_roles(
253 security: &TableSecurity,
254 privilege: TableAclPrivilege,
255) -> BTreeSet<String> {
256 let mut reachable = BTreeSet::from([security.role_owner.clone()]);
257 let Some(acl) = security.acl.as_ref() else {
258 return reachable;
259 };
260 loop {
261 let mut changed = false;
262 for entry in acl {
263 if entry.role != "PUBLIC"
264 && entry.grant_options.intersects(privilege.mask())
265 && reachable.contains(acl_grantor(entry, &security.role_owner))
266 {
267 changed |= reachable.insert(entry.role.clone());
268 }
269 }
270 if !changed {
271 return reachable;
272 }
273 }
274}
275
276pub fn select_acl_grantor(
277 security: &TableSecurity,
278 privilege: TableAclPrivilege,
279 current_user: &str,
280 roles: &BTreeMap<String, RoleDefinition>,
281 memberships: &BTreeMap<RoleMembershipKey, RoleMembership>,
282) -> Option<String> {
283 if role_inherits(roles, memberships, current_user, &security.role_owner) {
284 return Some(security.role_owner.clone());
285 }
286 let grant_options = grant_option_roles(security, privilege);
287 if grant_options.contains(current_user) {
288 return Some(current_user.to_string());
289 }
290 security.acl.as_ref().and_then(|acl| {
291 acl.iter()
292 .filter(|entry| entry.role != "PUBLIC" && grant_options.contains(&entry.role))
293 .find(|entry| role_inherits(roles, memberships, current_user, &entry.role))
294 .map(|entry| entry.role.clone())
295 })
296}
297
298pub fn role_has_privilege(
299 security: &TableSecurity,
300 subject: &str,
301 check: TablePrivilegeCheck,
302 roles: &BTreeMap<String, RoleDefinition>,
303 memberships: &BTreeMap<RoleMembershipKey, RoleMembership>,
304) -> bool {
305 if roles
306 .get(subject)
307 .is_some_and(|role| role.has(RoleAttribute::Superuser))
308 || role_inherits(roles, memberships, subject, &security.role_owner)
309 {
310 return true;
311 }
312 if check.grant_option {
313 return grant_option_roles(security, check.privilege)
314 .iter()
315 .any(|role| role_inherits(roles, memberships, subject, role));
316 }
317 match security.acl.as_ref() {
318 None => false,
319 Some(acl) => acl.iter().any(|entry| {
320 entry.privileges.intersects(check.privilege.mask())
321 && (entry.role == "PUBLIC"
322 || role_inherits(roles, memberships, subject, &entry.role))
323 }),
324 }
325}
326
327pub fn grant_acl(
328 security: &mut TableSecurity,
329 privilege: TableAclPrivilege,
330 grantees: &[String],
331 grantor: &str,
332 grant_option: bool,
333) {
334 materialize_acl(security);
335 let owner = security.role_owner.clone();
336 let acl = security.acl.as_mut().expect("table ACL was materialized");
337 for grantee in grantees {
338 let position = acl
339 .iter()
340 .position(|entry| entry.role == *grantee && acl_grantor(entry, &owner) == grantor)
341 .unwrap_or_else(|| {
342 acl.push(TableAclEntry {
343 role: grantee.clone(),
344 grantor: Some(grantor.to_string()),
345 privileges: TablePrivileges::default(),
346 grant_options: TablePrivileges::default(),
347 });
348 acl.len() - 1
349 });
350 let entry = &mut acl[position];
351 entry.privileges.insert(privilege.mask());
352 if grant_option && grantee != "PUBLIC" && grantee != &owner {
353 entry.grant_options.insert(privilege.mask());
354 }
355 }
356}
357
358pub fn revoke_acl(
359 security: &mut TableSecurity,
360 privilege: TableAclPrivilege,
361 grantees: &[String],
362 grantor: &str,
363 grant_option_only: bool,
364 cascade: bool,
365) -> Result<(), SQLError> {
366 let before = grant_option_roles(security, privilege);
367 materialize_acl(security);
368 let owner = security.role_owner.clone();
369 let acl = security.acl.as_mut().expect("table ACL was materialized");
370 for entry in acl
371 .iter_mut()
372 .filter(|entry| grantees.contains(&entry.role) && acl_grantor(entry, &owner) == grantor)
373 {
374 entry.grant_options.remove(privilege.mask());
375 if !grant_option_only {
376 entry.privileges.remove(privilege.mask());
377 }
378 }
379 remove_empty_entries(acl);
380 revoke_dependent_acl(security, privilege, &before, cascade)
381}
382
383fn revoke_dependent_acl(
384 security: &mut TableSecurity,
385 privilege: TableAclPrivilege,
386 before: &BTreeSet<String>,
387 cascade: bool,
388) -> Result<(), SQLError> {
389 loop {
390 let current = grant_option_roles(security, privilege);
391 let lost = before
392 .difference(¤t)
393 .cloned()
394 .collect::<BTreeSet<_>>();
395 if lost.is_empty() {
396 return Ok(());
397 }
398 let owner = security.role_owner.clone();
399 let dependent = security.acl.as_ref().is_some_and(|acl| {
400 acl.iter().any(|entry| {
401 lost.contains(acl_grantor(entry, &owner))
402 && (entry.privileges.intersects(privilege.mask())
403 || entry.grant_options.intersects(privilege.mask()))
404 })
405 });
406 if !dependent {
407 return Ok(());
408 }
409 if !cascade {
410 return Err(SQLError::Routine {
411 sqlstate: "2BP01".into(),
412 message: "dependent privileges exist".into(),
413 });
414 }
415 let acl = security
416 .acl
417 .as_mut()
418 .expect("dependent table privileges require an explicit ACL");
419 for entry in acl
420 .iter_mut()
421 .filter(|entry| lost.contains(acl_grantor(entry, &owner)))
422 {
423 entry.privileges.remove(privilege.mask());
424 entry.grant_options.remove(privilege.mask());
425 }
426 remove_empty_entries(acl);
427 }
428}
429
430fn remove_empty_entries(acl: &mut Vec<TableAclEntry>) {
431 acl.retain(|entry| !entry.privileges.is_empty() || !entry.grant_options.is_empty());
432}
433
434pub fn rewrite_acl_owner(security: &mut TableSecurity, new_owner: &str) {
435 let old_owner = std::mem::replace(&mut security.role_owner, new_owner.to_string());
436 if let Some(acl) = security.acl.as_mut() {
437 rewrite_acl_entries_owner(acl, &old_owner, new_owner);
438 }
439 for column_acl in security.column_acls.values_mut() {
440 rewrite_acl_entries_owner(column_acl, &old_owner, new_owner);
441 }
442}
443
444fn rewrite_acl_entries_owner(acl: &mut Vec<TableAclEntry>, old_owner: &str, new_owner: &str) {
445 for entry in acl.iter_mut() {
446 if entry.role == old_owner {
447 entry.role = new_owner.to_string();
448 }
449 if entry.grantor.as_deref() == Some(old_owner) {
450 entry.grantor = Some(new_owner.to_string());
451 }
452 }
453 let mut merged: Vec<TableAclEntry> = Vec::with_capacity(acl.len());
454 for entry in std::mem::take(acl) {
455 if let Some(existing) = merged.iter_mut().find(|existing| {
456 existing.role == entry.role
457 && acl_grantor(existing, new_owner) == acl_grantor(&entry, new_owner)
458 }) {
459 existing.privileges.insert(entry.privileges);
460 existing.grant_options.insert(entry.grant_options);
461 } else {
462 merged.push(entry);
463 }
464 }
465 *acl = merged;
466}
467
468pub fn role_can_view_table(
469 security: &TableSecurity,
470 subject: &str,
471 roles: &BTreeMap<String, RoleDefinition>,
472 memberships: &BTreeMap<RoleMembershipKey, RoleMembership>,
473) -> bool {
474 TableAclPrivilege::ALL.into_iter().any(|privilege| {
475 role_has_privilege(
476 security,
477 subject,
478 TablePrivilegeCheck {
479 privilege,
480 grant_option: false,
481 },
482 roles,
483 memberships,
484 )
485 }) || security.column_acls.iter().any(|(column, _)| {
486 TableAclPrivilege::COLUMN_ALL.into_iter().any(|privilege| {
487 super::columns::role_has_column_privilege(
488 security,
489 column,
490 subject,
491 TablePrivilegeCheck {
492 privilege,
493 grant_option: false,
494 },
495 roles,
496 memberships,
497 )
498 })
499 })
500}
501
502pub fn role_has_table_privilege(
503 security: &TableSecurity,
504 subject: &str,
505 privilege: TableAclPrivilege,
506 roles: &BTreeMap<String, RoleDefinition>,
507 memberships: &BTreeMap<RoleMembershipKey, RoleMembership>,
508) -> bool {
509 role_has_privilege(
510 security,
511 subject,
512 TablePrivilegeCheck {
513 privilege,
514 grant_option: false,
515 },
516 roles,
517 memberships,
518 )
519}
520
521pub fn role_has_column_privilege(
522 security: &TableSecurity,
523 column: &str,
524 subject: &str,
525 privilege: TableAclPrivilege,
526 roles: &BTreeMap<String, RoleDefinition>,
527 memberships: &BTreeMap<RoleMembershipKey, RoleMembership>,
528) -> bool {
529 super::columns::role_has_column_privilege(
530 security,
531 column,
532 subject,
533 TablePrivilegeCheck {
534 privilege,
535 grant_option: false,
536 },
537 roles,
538 memberships,
539 )
540}
541
542mod invariants;
543pub use invariants::validate_table_security_invariants;