uqa_sql/catalog/security/
schema_binding.rs1use super::{
10 role_bindings::{bind_role, role_name},
11 SchemaSecurity,
12};
13use crate::catalog::roles::{RoleDefinition, RoleIdentity};
14use std::collections::BTreeMap;
15use uqa_core::{
16 catalog_acl::AclGrantee,
17 catalog_role::BoundAclEntry,
18 catalog_schema::{BoundSchemaRow, SchemaAclEntry, SchemaPrivileges, SchemaTupleIdentity},
19};
20
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct BoundSchemaSecurity {
23 pub tuple: Option<SchemaTupleIdentity>,
24 pub role_owner: RoleIdentity,
25 pub acl: Option<Vec<BoundAclEntry<SchemaPrivileges>>>,
26}
27
28impl BoundSchemaSecurity {
29 pub fn namespace_oid(&self, name: &str) -> i64 {
30 self.tuple
31 .map_or_else(|| crate::catalog::oids::schema_oid(name), |tuple| tuple.oid)
32 }
33 pub fn owner(role_owner: RoleIdentity) -> Self {
34 Self {
35 tuple: None,
36 role_owner,
37 acl: None,
38 }
39 }
40
41 pub fn bootstrap(name: &str) -> Self {
42 let mut security = Self::from_row(BoundSchemaRow::bootstrap(name)).1;
43 security.tuple = Some(SchemaTupleIdentity::initial(
44 u32::try_from(crate::catalog::oids::schema_oid(name)).expect("bootstrap schema OID"),
45 ));
46 security
47 }
48
49 pub fn with_public_privileges(create: bool) -> Self {
50 let owner = RoleIdentity::BOOTSTRAP;
51 Self {
52 tuple: None,
53 role_owner: owner,
54 acl: Some(vec![
55 BoundAclEntry {
56 role: Some(owner),
57 grantor: owner,
58 privileges: SchemaPrivileges::ALL,
59 grant_options: SchemaPrivileges::default(),
60 },
61 BoundAclEntry {
62 role: None,
63 grantor: owner,
64 privileges: SchemaPrivileges {
65 usage: true,
66 create,
67 },
68 grant_options: SchemaPrivileges::default(),
69 },
70 ]),
71 }
72 }
73
74 pub fn bind(
75 security: &SchemaSecurity,
76 roles: &BTreeMap<String, RoleDefinition>,
77 ) -> Result<Self, String> {
78 let bind = |name: &str| bind_role(roles, name, "schema");
79 Ok(Self {
80 tuple: None,
81 role_owner: bind(&security.role_owner)?,
82 acl: security
83 .acl
84 .as_ref()
85 .map(|entries| {
86 entries
87 .iter()
88 .map(|entry| {
89 Ok(BoundAclEntry {
90 role: entry.role.role_name().map(bind).transpose()?,
91 grantor: bind(
92 entry.grantor.as_deref().unwrap_or(&security.role_owner),
93 )?,
94 privileges: entry.privileges,
95 grant_options: entry.grant_options,
96 })
97 })
98 .collect::<Result<_, String>>()
99 })
100 .transpose()?,
101 })
102 }
103
104 pub fn resolve(
105 &self,
106 roles: &BTreeMap<String, RoleDefinition>,
107 ) -> Result<SchemaSecurity, String> {
108 let name = |identity| role_name(roles, identity, "schema").map(str::to_owned);
109 Ok(SchemaSecurity {
110 role_owner: name(self.role_owner)?,
111 acl: self
112 .acl
113 .as_ref()
114 .map(|entries| {
115 entries
116 .iter()
117 .map(|entry| {
118 Ok(SchemaAclEntry {
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<_, String>>()
130 })
131 .transpose()?,
132 })
133 }
134
135 pub fn validate(&self, roles: &BTreeMap<String, RoleDefinition>) -> Result<(), String> {
136 if self.tuple.is_some_and(|tuple| !tuple.is_valid()) {
137 return Err("invalid schema catalog tuple identity".into());
138 }
139 role_name(roles, self.role_owner, "schema")?;
140 for entry in self.acl.iter().flatten() {
141 if let Some(grantee) = entry.role {
142 role_name(roles, grantee, "schema")?;
143 }
144 role_name(roles, entry.grantor, "schema")?;
145 }
146 Ok(())
147 }
148
149 pub fn depends_on(&self, role: RoleIdentity) -> bool {
150 self.role_owner == role
151 || self
152 .acl
153 .iter()
154 .flatten()
155 .any(|entry| entry.role == Some(role) || entry.grantor == role)
156 }
157
158 pub fn from_row(row: BoundSchemaRow) -> (String, Self) {
159 (
160 row.name,
161 Self {
162 tuple: row.tuple,
163 role_owner: row.role_owner,
164 acl: row.acl,
165 },
166 )
167 }
168
169 pub fn row(&self, name: impl Into<String>) -> BoundSchemaRow {
170 BoundSchemaRow {
171 name: name.into(),
172 tuple: self.tuple,
173 role_owner: self.role_owner,
174 acl: self.acl.clone(),
175 }
176 }
177}
178
179#[cfg(test)]
180mod tests;