1use super::{registration::RoutineSupportAuthority, routine_kind, routine_local_name};
10use crate::catalog::roles::identity::RoleSubject;
11use crate::catalog::roles::{RoleIdentity, RoleReference};
12
13pub mod binding;
14use crate::{
15 ast::{
16 AlterRoutineOwnerStmt, AlterRoutineStmt, CreateFunction, GrantRoutineStmt, RoutineAclEntry,
17 },
18 catalog::roles::{role_inherits, RoleDefinition, RoleMembership, RoleMembershipKey},
19 SQLError,
20};
21use std::collections::{BTreeMap, BTreeSet};
22use uqa_core::catalog_acl::AclGrantee;
23
24pub trait RoutineExecutionAuthority: RoutineSupportAuthority {
25 fn current_role(&self) -> RoleReference;
26 fn current_user_has_role_identity_privileges(
27 &self,
28 role: crate::catalog::roles::RoleIdentity,
29 ) -> bool;
30}
31
32pub fn routine_owner_identity(stmt: &AlterRoutineOwnerStmt) -> AlterRoutineStmt {
33 AlterRoutineStmt {
34 kind: stmt.kind,
35 name: stmt.name.clone(),
36 arg_types: stmt.arg_types.clone(),
37 arg_type_references: stmt.arg_type_references.clone(),
38 volatility: None,
39 strict: None,
40 security_definer: None,
41 leakproof: None,
42 parallel: None,
43 support: None,
44 config_actions: Vec::new(),
45 }
46}
47
48pub fn ensure_routine_execute_privilege(
49 authority: &dyn RoutineExecutionAuthority,
50 definition: &CreateFunction,
51) -> Result<(), SQLError> {
52 ensure_routine_execute_privilege_named(
53 authority,
54 definition,
55 &routine_local_name(&definition.name)?,
56 )
57}
58
59pub fn ensure_routine_execute_privilege_named(
60 authority: &dyn RoutineExecutionAuthority,
61 definition: &CreateFunction,
62 display_name: &str,
63) -> Result<(), SQLError> {
64 let allowed = routine_privilege_allowed(
65 &bound_routine_owner(definition)?,
66 definition.execute_acl.as_deref(),
67 false,
68 authority.current_user_is_superuser(),
69 |role| authority.current_user_has_role_identity_privileges(*role),
70 );
71 if allowed {
72 Ok(())
73 } else {
74 Err(SQLError::Routine {
75 sqlstate: "42501".into(),
76 message: format!(
77 "permission denied for {} {}",
78 routine_kind(definition),
79 display_name
80 ),
81 })
82 }
83}
84
85pub fn routine_privilege_allowed(
87 owner: &RoleIdentity,
88 acl: Option<&[RoutineAclEntry]>,
89 grant_option: bool,
90 superuser: bool,
91 has_role: impl Fn(&RoleIdentity) -> bool,
92) -> bool {
93 if superuser || (grant_option && has_role(owner)) {
94 return true;
95 }
96 acl.map_or(!grant_option, |acl| {
97 acl.iter().any(|entry| {
98 (!grant_option || entry.grant_option)
99 && ((entry.role.is_none() && !grant_option)
100 || entry.role.as_ref().is_some_and(&has_role))
101 })
102 })
103}
104
105pub fn bound_routine_owner(definition: &CreateFunction) -> Result<RoleIdentity, SQLError> {
106 definition
107 .owner
108 .filter(|owner| owner.is_valid())
109 .ok_or_else(|| {
110 SQLError::Internal(format!(
111 "routine `{}` has no bound catalog owner",
112 definition.name
113 ))
114 })
115}
116
117pub fn validate_routine_acl_roles(
118 stmt: &GrantRoutineStmt,
119 grantees: &[AclGrantee],
120 requested_grantor: Option<&str>,
121 current_user: &(impl RoleSubject + ?Sized),
122 roles: &BTreeMap<String, RoleDefinition>,
123) -> Result<(), SQLError> {
124 for role in grantees {
125 if role
126 .role_name()
127 .is_some_and(|name| !roles.contains_key(name))
128 {
129 return Err(SQLError::Routine {
130 sqlstate: "42704".into(),
131 message: format!("role \"{role}\" does not exist"),
132 });
133 }
134 }
135 if stmt.is_grant && stmt.grant_option && grantees.iter().any(AclGrantee::is_public) {
136 return Err(SQLError::Routine {
137 sqlstate: "0LP01".into(),
138 message: "grant options can only be granted to roles".into(),
139 });
140 }
141 if let Some(requested_grantor) = requested_grantor {
142 if !roles.contains_key(requested_grantor) {
143 return Err(SQLError::Routine {
144 sqlstate: "42704".into(),
145 message: format!("role \"{requested_grantor}\" does not exist"),
146 });
147 }
148 if current_user.role_name(roles) != Some(requested_grantor) {
149 return Err(SQLError::Routine {
150 sqlstate: "0A000".into(),
151 message: "grantor must be current user".into(),
152 });
153 }
154 }
155 Ok(())
156}
157
158fn materialize_routine_acl(
159 definition: &mut CreateFunction,
160) -> Result<&mut Vec<RoutineAclEntry>, SQLError> {
161 let owner = bound_routine_owner(definition)?;
162 Ok(definition.execute_acl.get_or_insert_with(|| {
163 vec![
164 RoutineAclEntry {
165 role: None,
166 grantor: owner,
167 grant_option: false,
168 },
169 RoutineAclEntry {
170 role: Some(owner),
171 grantor: owner,
172 grant_option: false,
173 },
174 ]
175 }))
176}
177
178fn routine_grant_option_roles(
179 definition: &CreateFunction,
180) -> Result<BTreeSet<RoleIdentity>, SQLError> {
181 Ok(routine_grant_option_roles_for(
182 bound_routine_owner(definition)?,
183 definition.execute_acl.as_deref(),
184 ))
185}
186
187pub(super) fn routine_grant_option_roles_for(
188 owner: RoleIdentity,
189 acl: Option<&[RoutineAclEntry]>,
190) -> BTreeSet<RoleIdentity> {
191 let mut reachable = BTreeSet::from([owner]);
192 let Some(acl) = acl else {
193 return reachable;
194 };
195 loop {
196 let mut changed = false;
197 for entry in acl {
198 if let Some(role) = entry.role {
199 if entry.grant_option && reachable.contains(&entry.grantor) {
200 changed |= reachable.insert(role);
201 }
202 }
203 }
204 if !changed {
205 return reachable;
206 }
207 }
208}
209
210pub fn select_routine_acl_grantor(
211 definition: &CreateFunction,
212 current_user: &(impl RoleSubject + ?Sized),
213 roles: &BTreeMap<String, RoleDefinition>,
214 memberships: &BTreeMap<RoleMembershipKey, RoleMembership>,
215) -> Result<Option<RoleIdentity>, SQLError> {
216 let owner = bound_routine_owner(definition)?;
217 if role_inherits(roles, memberships, current_user, &owner) {
218 return Ok(Some(owner));
219 }
220 let grant_options = routine_grant_option_roles(definition)?;
221 if let Some(identity) = current_user
222 .role_definition(roles)
223 .map(RoleDefinition::identity)
224 .filter(|identity| grant_options.contains(identity))
225 {
226 return Ok(Some(identity));
227 }
228 Ok(definition.execute_acl.as_ref().and_then(|acl| {
229 acl.iter()
230 .filter_map(|entry| entry.role)
231 .filter(|role| grant_options.contains(role))
232 .find(|role| role_inherits(roles, memberships, current_user, role))
233 }))
234}
235
236pub fn grant_routine_acl(
237 definition: &mut CreateFunction,
238 grantee: Option<RoleIdentity>,
239 grantor: RoleIdentity,
240 grant_option: bool,
241) -> Result<(), SQLError> {
242 let owner = bound_routine_owner(definition)?;
243 if definition.execute_acl.is_none() && grantee.is_none() && grantor == owner && !grant_option {
244 return Ok(());
245 }
246 let acl = materialize_routine_acl(definition)?;
247 if let Some(entry) = acl
248 .iter_mut()
249 .find(|entry| entry.role == grantee && entry.grantor == grantor)
250 {
251 entry.grant_option |= grant_option;
252 } else {
253 acl.push(RoutineAclEntry {
254 role: grantee,
255 grantor,
256 grant_option,
257 });
258 }
259 Ok(())
260}
261
262pub fn revoke_routine_acl(
263 definition: &mut CreateFunction,
264 grantee: Option<RoleIdentity>,
265 grantor: RoleIdentity,
266 grant_option_only: bool,
267 cascade: bool,
268) -> Result<bool, SQLError> {
269 let before_grant_options = routine_grant_option_roles(definition)?;
270 let acl = materialize_routine_acl(definition)?;
271 let Some(position) = acl
272 .iter()
273 .position(|entry| entry.role == grantee && entry.grantor == grantor)
274 else {
275 return Ok(false);
276 };
277 if grant_option_only {
278 if !acl[position].grant_option {
279 return Ok(false);
280 }
281 acl[position].grant_option = false;
282 } else {
283 acl.remove(position);
284 }
285 revoke_dependent_routine_acl(definition, &before_grant_options, cascade)?;
286 Ok(true)
287}
288
289fn revoke_dependent_routine_acl(
290 definition: &mut CreateFunction,
291 before_grant_options: &BTreeSet<RoleIdentity>,
292 cascade: bool,
293) -> Result<(), SQLError> {
294 loop {
295 let current_grant_options = routine_grant_option_roles(definition)?;
296 let lost = before_grant_options
297 .difference(¤t_grant_options)
298 .copied()
299 .collect::<BTreeSet<_>>();
300 if lost.is_empty() {
301 return Ok(());
302 }
303 let dependent_exists = definition
304 .execute_acl
305 .as_ref()
306 .is_some_and(|acl| acl.iter().any(|entry| lost.contains(&entry.grantor)));
307 if !dependent_exists {
308 return Ok(());
309 }
310 if !cascade {
311 return Err(SQLError::Routine {
312 sqlstate: "2BP01".into(),
313 message: "dependent privileges exist".into(),
314 });
315 }
316 definition
317 .execute_acl
318 .as_mut()
319 .expect("dependent ACLs require an explicit ACL")
320 .retain(|entry| !lost.contains(&entry.grantor));
321 }
322}
323
324pub fn rewrite_routine_acl_owner(
325 definition: &mut CreateFunction,
326 old_owner: RoleIdentity,
327 new_owner: RoleIdentity,
328) {
329 let Some(acl) = definition.execute_acl.as_mut() else {
330 return;
331 };
332 for entry in acl.iter_mut() {
333 if entry.role == Some(old_owner) {
334 entry.role = Some(new_owner);
335 }
336 if entry.grantor == old_owner {
337 entry.grantor = new_owner;
338 }
339 }
340 let mut merged: Vec<RoutineAclEntry> = Vec::with_capacity(acl.len());
341 for entry in std::mem::take(acl) {
342 if let Some(existing) = merged
343 .iter_mut()
344 .find(|existing| existing.role == entry.role && existing.grantor == entry.grantor)
345 {
346 existing.grant_option |= entry.grant_option;
347 } else {
348 merged.push(entry);
349 }
350 }
351 *acl = merged;
352}
353
354pub fn routine_acl_warning(is_grant: bool, name: &str) -> (&'static str, String) {
355 let local_name = name.rsplit('.').next().unwrap_or(name);
356 (
357 "WARNING",
358 if is_grant {
359 format!("no privileges were granted for \"{local_name}\"")
360 } else {
361 format!("no privileges could be revoked for \"{local_name}\"")
362 },
363 )
364}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369
370 fn role(value: u8) -> RoleIdentity {
371 RoleIdentity {
372 oid: i64::from(value),
373 object_id: [value; 16],
374 }
375 }
376 fn grant(grantee: u8, grantor: u8) -> RoutineAclEntry {
377 RoutineAclEntry {
378 role: Some(role(grantee)),
379 grantor: role(grantor),
380 grant_option: true,
381 }
382 }
383
384 #[test]
385 fn routine_grant_option_reachability_requires_an_owner_root() {
386 let disconnected_cycle = [grant(2, 3), grant(3, 2)];
387 assert_eq!(
388 routine_grant_option_roles_for(role(1), Some(&disconnected_cycle)),
389 BTreeSet::from([role(1)])
390 );
391 let rooted_cycle = [grant(2, 1), grant(3, 2), grant(2, 3)];
392 assert_eq!(
393 routine_grant_option_roles_for(role(1), Some(&rooted_cycle)),
394 BTreeSet::from([role(1), role(2), role(3)])
395 );
396 }
397
398 #[test]
399 fn routine_grant_option_reachability_accepts_an_independent_owner_path() {
400 let acl = [grant(2, 1), grant(3, 2), grant(3, 1), grant(4, 3)];
401 assert_eq!(
402 routine_grant_option_roles_for(role(1), Some(&acl[2..])),
403 BTreeSet::from([role(1), role(3), role(4)])
404 );
405 }
406}