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