uqa_sql/catalog/security/
table_binding.rs1use super::{role_bindings, TableAclEntry, TablePrivileges, TableSecurity};
10use crate::catalog::roles::{identity::RoleBinding, RoleDefinition, RoleIdentity, RoleReference};
11use std::collections::BTreeMap;
12use uqa_core::catalog_acl::AclGrantee;
13use uqa_core::catalog_role::BoundAclEntry;
14
15pub type BoundTableAclEntry = BoundAclEntry<TablePrivileges>;
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct BoundTableSecurity {
19 pub role_owner: RoleIdentity,
20 pub acl: Option<Vec<BoundTableAclEntry>>,
21 pub column_acls: BTreeMap<String, Vec<BoundTableAclEntry>>,
22 pub acl_revisions: uqa_core::catalog_acl::RelationAclRevisions,
23}
24
25impl BoundTableSecurity {
26 pub fn remove_column_acl(&mut self, column: &str) {
27 self.column_acls.remove(column);
28 self.acl_revisions.columns.remove(column);
29 }
30
31 pub fn rename_column_acl(&mut self, from: &str, to: &str) {
32 if let Some(acl) = self.column_acls.remove(from) {
33 self.column_acls.insert(to.to_owned(), acl);
34 }
35 if let Some(revision) = self.acl_revisions.columns.remove(from) {
36 self.acl_revisions.columns.insert(to.to_owned(), revision);
37 }
38 }
39
40 pub fn from_row(row: uqa_core::catalog_acl::BoundRelationSecurity) -> Self {
41 Self {
42 role_owner: row.role_owner,
43 acl: row.acl,
44 column_acls: row.column_acls,
45 acl_revisions: row.acl_revisions,
46 }
47 }
48
49 pub fn row(&self) -> uqa_core::catalog_acl::BoundRelationSecurity {
50 uqa_core::catalog_acl::BoundRelationSecurity {
51 role_owner: self.role_owner,
52 acl: self.acl.clone(),
53 column_acls: self.column_acls.clone(),
54 acl_revisions: self.acl_revisions.clone(),
55 }
56 }
57
58 pub fn owner(role_owner: RoleIdentity) -> Self {
59 Self {
60 role_owner,
61 acl: None,
62 column_acls: BTreeMap::new(),
63 acl_revisions: uqa_core::catalog_acl::RelationAclRevisions::default(),
64 }
65 }
66
67 pub fn owner_reference(
68 &self,
69 roles: &BTreeMap<String, RoleDefinition>,
70 ) -> Result<RoleReference, crate::SQLError> {
71 let name = role_bindings::role_name(roles, self.role_owner, "table")
72 .map_err(crate::SQLError::Internal)?;
73 Ok(RoleReference::Bound(std::sync::Arc::new(
74 RoleBinding::from_definition(&roles[name])?,
75 )))
76 }
77
78 pub fn bind(
79 security: &TableSecurity,
80 roles: &BTreeMap<String, RoleDefinition>,
81 ) -> Result<Self, String> {
82 let bind = |name: &str| role_bindings::bind_role(roles, name, "table");
83 let entries = |entries: &[TableAclEntry]| {
84 entries
85 .iter()
86 .map(|entry| {
87 Ok(BoundTableAclEntry {
88 role: entry.role.role_name().map(bind).transpose()?,
89 grantor: bind(entry.grantor.as_deref().unwrap_or(&security.role_owner))?,
90 privileges: entry.privileges,
91 grant_options: entry.grant_options,
92 })
93 })
94 .collect::<Result<Vec<_>, String>>()
95 };
96 Ok(Self {
97 role_owner: bind(&security.role_owner)?,
98 acl_revisions: uqa_core::catalog_acl::RelationAclRevisions::default(),
99 acl: security.acl.as_deref().map(entries).transpose()?,
100 column_acls: security
101 .column_acls
102 .iter()
103 .map(|(column, acl)| Ok((column.clone(), entries(acl)?)))
104 .collect::<Result<_, String>>()?,
105 })
106 }
107
108 pub fn resolve(
110 &self,
111 roles: &BTreeMap<String, RoleDefinition>,
112 ) -> Result<TableSecurity, String> {
113 let name = |identity| role_bindings::role_name(roles, identity, "table").map(str::to_owned);
114 let entries = |entries: &[BoundTableAclEntry]| {
115 entries
116 .iter()
117 .map(|entry| {
118 Ok(TableAclEntry {
119 role: entry
120 .role
121 .map(&name)
122 .transpose()?
123 .map_or(AclGrantee::Public, AclGrantee::Role),
124 grantor: Some(name(entry.grantor)?),
125 privileges: entry.privileges,
126 grant_options: entry.grant_options,
127 })
128 })
129 .collect::<Result<Vec<_>, String>>()
130 };
131 Ok(TableSecurity {
132 role_owner: name(self.role_owner)?,
133 acl: self.acl.as_deref().map(entries).transpose()?,
134 column_acls: self
135 .column_acls
136 .iter()
137 .map(|(column, acl)| Ok((column.clone(), entries(acl)?)))
138 .collect::<Result<_, String>>()?,
139 })
140 }
141
142 pub fn validate(
143 &self,
144 columns: Option<&[String]>,
145 roles: &BTreeMap<String, RoleDefinition>,
146 ) -> Result<(), String> {
147 if self.acl_revisions.relation == Some([0; 16])
148 || self.acl_revisions.columns.iter().any(|(column, revision)| {
149 *revision == [0; 16] || columns.is_some_and(|columns| !columns.contains(column))
150 })
151 {
152 return Err("invalid relation or attribute ACL tuple identity".into());
153 }
154 super::table::validate_table_security_invariants(&self.resolve(roles)?, columns, roles)
155 }
156
157 pub fn depends_on(&self, role: RoleIdentity) -> bool {
158 self.role_owner == role
159 || self
160 .acl
161 .iter()
162 .flatten()
163 .chain(self.column_acls.values().flatten())
164 .any(|entry| entry.role == Some(role) || entry.grantor == role)
165 }
166}
167
168#[cfg(test)]
169mod tests;