uqa_sql/routines/security/
binding.rs1use super::{bound_routine_owner, routine_grant_option_roles_for};
10use crate::{
11 ast::{CreateFunction, RoutineAclEntry},
12 catalog::roles::{identity::RoleSubject, RoleDefinition, RoleIdentity, RoleReference},
13 SQLError,
14};
15use serde::Deserialize;
16use std::collections::{BTreeMap, BTreeSet};
17use uqa_core::catalog_acl::AclGrantee;
18
19#[derive(Deserialize)]
21pub struct LegacyRoutineAclEntry {
22 pub role: AclGrantee,
23 #[serde(default)]
24 pub grantor: Option<String>,
25 pub grant_option: bool,
26}
27
28pub struct BoundRoutineAuthority {
29 pub owner: RoleIdentity,
30 pub execute_acl: Option<Vec<RoutineAclEntry>>,
31}
32
33pub fn bind_legacy_authority(
34 owner: &str,
35 acl: Option<Vec<LegacyRoutineAclEntry>>,
36 roles: &BTreeMap<String, RoleDefinition>,
37 implicit_owner_execute: bool,
38) -> Result<BoundRoutineAuthority, SQLError> {
39 let bind = |name: &str| {
40 RoleReference::from(name)
41 .bind(roles)
42 .map(|role| role.identity())
43 };
44 let identity = bind(owner)?;
45 let mut execute_acl = acl
46 .map(|acl| {
47 acl.into_iter()
48 .map(|entry| {
49 Ok(RoutineAclEntry {
50 role: entry.role.role_name().map(bind).transpose()?,
51 grantor: bind(entry.grantor.as_deref().unwrap_or(owner))?,
52 grant_option: entry.grant_option,
53 })
54 })
55 .collect::<Result<Vec<_>, SQLError>>()
56 })
57 .transpose()?;
58 if implicit_owner_execute {
59 if let Some(acl) = execute_acl.as_mut() {
60 if !acl.iter().any(|entry| entry.role == Some(identity)) {
61 acl.push(RoutineAclEntry {
62 role: Some(identity),
63 grantor: identity,
64 grant_option: false,
65 });
66 }
67 }
68 }
69 validate_acl(identity, execute_acl.as_deref())?;
70 Ok(BoundRoutineAuthority {
71 owner: identity,
72 execute_acl,
73 })
74}
75
76fn invalid(message: &str) -> SQLError {
77 SQLError::Internal(format!("invalid routine authority: {message}"))
78}
79
80fn validate_acl(owner: RoleIdentity, acl: Option<&[RoutineAclEntry]>) -> Result<(), SQLError> {
81 if !owner.is_valid() {
82 return Err(invalid("missing owner incarnation"));
83 }
84 let reachable = routine_grant_option_roles_for(owner, acl);
85 let mut paths = BTreeSet::new();
86 for entry in acl.into_iter().flatten() {
87 if !entry.grantor.is_valid() || entry.role.is_some_and(|role| !role.is_valid()) {
88 return Err(invalid("missing ACL endpoint incarnation"));
89 }
90 if entry.role.is_none() && entry.grant_option {
91 return Err(invalid("PUBLIC cannot retain a grant option"));
92 }
93 if !paths.insert((entry.role, entry.grantor)) {
94 return Err(invalid("duplicate ACL grant path"));
95 }
96 if !reachable.contains(&entry.grantor) {
97 return Err(invalid("ACL grantor has no owner-rooted grant option"));
98 }
99 }
100 Ok(())
101}
102
103pub fn validate_routine_authority_identities(definition: &CreateFunction) -> Result<(), SQLError> {
104 validate_acl(
105 bound_routine_owner(definition)?,
106 definition.execute_acl.as_deref(),
107 )
108}
109
110fn authority_roles(definition: &CreateFunction) -> Result<BTreeSet<RoleIdentity>, SQLError> {
111 let owner = bound_routine_owner(definition)?;
112 let mut identities = BTreeSet::from([owner]);
113 for entry in definition.execute_acl.iter().flatten() {
114 identities.extend(entry.role);
115 identities.insert(entry.grantor);
116 }
117 Ok(identities)
118}
119
120pub fn routine_role_dependencies(
121 definition: &CreateFunction,
122 roles: &BTreeMap<String, RoleDefinition>,
123) -> Result<BTreeSet<String>, SQLError> {
124 validate_routine_authority_identities(definition)?;
125 authority_roles(definition)?
126 .into_iter()
127 .map(|identity| {
128 identity
129 .role_name(roles)
130 .map(str::to_owned)
131 .ok_or_else(|| invalid("missing role incarnation"))
132 })
133 .collect()
134}
135
136pub fn validate_routine_authority(
137 definition: &CreateFunction,
138 roles: &BTreeMap<String, RoleDefinition>,
139) -> Result<(), SQLError> {
140 routine_role_dependencies(definition, roles).map(|_| ())
141}
142
143pub fn bind_routine_grantees(
144 grantees: &[AclGrantee],
145 roles: &BTreeMap<String, RoleDefinition>,
146) -> Result<Vec<Option<RoleIdentity>>, SQLError> {
147 grantees
148 .iter()
149 .map(|grantee| {
150 grantee
151 .role_name()
152 .map(|name| {
153 RoleReference::from(name)
154 .bind(roles)
155 .map(|role| role.identity())
156 })
157 .transpose()
158 })
159 .collect()
160}
161
162pub fn added_routine_acl_roles(
163 before: &CreateFunction,
164 after: &CreateFunction,
165 roles: &BTreeMap<String, RoleDefinition>,
166 added: &mut BTreeSet<String>,
167) -> Result<(), SQLError> {
168 let mut old = authority_roles(before)?;
169 old.remove(&bound_routine_owner(before)?);
170 let mut new = authority_roles(after)?;
171 new.remove(&bound_routine_owner(after)?);
172 validate_routine_authority(after, roles)?;
173 for identity in new.difference(&old) {
174 added.insert(
175 identity
176 .role_name(roles)
177 .ok_or_else(|| invalid("missing ACL incarnation"))?
178 .to_owned(),
179 );
180 }
181 Ok(())
182}
183
184#[cfg(test)]
185mod tests;