Skip to main content

uqa_sql/routines/
security.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Routine execution authorization, owner transitions, and grant-option reachability.
8
9use super::{registration::RoutineSupportAuthority, routine_kind, routine_local_name};
10use crate::catalog::roles::identity::RoleSubject;
11use crate::catalog::roles::{RoleIdentity, RoleReference};
12
13pub mod binding;
14pub mod grants;
15use crate::{
16    ast::{
17        AlterRoutineOwnerStmt, AlterRoutineStmt, CreateFunction, GrantRoutineStmt, RoutineAclEntry,
18    },
19    catalog::roles::{RoleDefinition, RoleMembership, RoleMembershipKey},
20    catalog::security::object_acl,
21    SQLError,
22};
23use std::collections::BTreeMap;
24use uqa_core::catalog_acl::AclGrantee;
25
26pub trait RoutineExecutionAuthority: RoutineSupportAuthority {
27    fn current_role(&self) -> RoleReference;
28    fn current_user_has_role_identity_privileges(
29        &self,
30        role: crate::catalog::roles::RoleIdentity,
31    ) -> bool;
32}
33
34pub fn routine_owner_identity(stmt: &AlterRoutineOwnerStmt) -> AlterRoutineStmt {
35    AlterRoutineStmt {
36        kind: stmt.kind,
37        name: stmt.name.clone(),
38        arg_types: stmt.arg_types.clone(),
39        arg_type_references: stmt.arg_type_references.clone(),
40        volatility: None,
41        strict: None,
42        security_definer: None,
43        leakproof: None,
44        parallel: None,
45        support: None,
46        cost: None,
47        rows: None,
48        config_actions: Vec::new(),
49        attribute_clauses: crate::ast::RoutineAttributeClauses::default(),
50    }
51}
52
53pub fn ensure_routine_execute_privilege(
54    authority: &dyn RoutineExecutionAuthority,
55    definition: &CreateFunction,
56) -> Result<(), SQLError> {
57    ensure_routine_execute_privilege_named(
58        authority,
59        definition,
60        &routine_local_name(&definition.name)?,
61    )
62}
63
64pub fn ensure_routine_execute_privilege_named(
65    authority: &dyn RoutineExecutionAuthority,
66    definition: &CreateFunction,
67    display_name: &str,
68) -> Result<(), SQLError> {
69    let allowed = routine_privilege_allowed(
70        &bound_routine_owner(definition)?,
71        definition.execute_acl.as_deref(),
72        false,
73        authority.current_user_is_superuser(),
74        |role| authority.current_user_has_role_identity_privileges(*role),
75    );
76    if allowed {
77        Ok(())
78    } else {
79        Err(SQLError::Routine {
80            sqlstate: "42501".into(),
81            message: format!(
82                "permission denied for {} {}",
83                routine_kind(definition),
84                display_name
85            ),
86        })
87    }
88}
89
90/// Ownership retains grant options even when the owner's explicit EXECUTE was revoked.
91pub fn routine_privilege_allowed(
92    owner: &RoleIdentity,
93    acl: Option<&[RoutineAclEntry]>,
94    grant_option: bool,
95    superuser: bool,
96    has_role: impl Fn(&RoleIdentity) -> bool,
97) -> bool {
98    object_acl::privilege_allowed(owner, acl, grant_option, superuser, has_role)
99}
100
101pub fn bound_routine_owner(definition: &CreateFunction) -> Result<RoleIdentity, SQLError> {
102    definition
103        .owner
104        .filter(|owner| owner.is_valid())
105        .ok_or_else(|| {
106            SQLError::Internal(format!(
107                "routine `{}` has no bound catalog owner",
108                definition.name
109            ))
110        })
111}
112
113pub fn validate_routine_acl_roles(
114    _stmt: &GrantRoutineStmt,
115    grantees: &[AclGrantee],
116    requested_grantor: Option<&str>,
117    current_user: &(impl RoleSubject + ?Sized),
118    roles: &BTreeMap<String, RoleDefinition>,
119) -> Result<(), SQLError> {
120    for role in grantees {
121        if role
122            .role_name()
123            .is_some_and(|name| !roles.contains_key(name))
124        {
125            return Err(SQLError::Routine {
126                sqlstate: "42704".into(),
127                message: format!("role \"{role}\" does not exist"),
128            });
129        }
130    }
131    grants::validate_grantor(requested_grantor, current_user, roles)
132}
133
134pub fn select_routine_acl_grantor(
135    definition: &CreateFunction,
136    current_user: &(impl RoleSubject + ?Sized),
137    roles: &BTreeMap<String, RoleDefinition>,
138    memberships: &BTreeMap<RoleMembershipKey, RoleMembership>,
139) -> Result<Option<RoleIdentity>, SQLError> {
140    let owner = bound_routine_owner(definition)?;
141    let grantor = object_acl::select_grantor(
142        owner,
143        definition.execute_acl.as_deref(),
144        current_user,
145        roles,
146        memberships,
147    );
148    if grantor.is_none()
149        && !routine_privilege_allowed(
150            &owner,
151            definition.execute_acl.as_deref(),
152            false,
153            false,
154            |role| crate::catalog::roles::role_inherits(roles, memberships, current_user, role),
155        )
156    {
157        return Err(SQLError::Routine {
158            sqlstate: "42501".into(),
159            message: format!(
160                "permission denied for function {}",
161                routine_local_name(&definition.name)?
162            ),
163        });
164    }
165    Ok(grantor)
166}
167
168pub fn grant_routine_acl(
169    definition: &mut CreateFunction,
170    grantee: Option<RoleIdentity>,
171    grantor: RoleIdentity,
172    grant_option: bool,
173) -> Result<(), SQLError> {
174    let owner = bound_routine_owner(definition)?;
175    object_acl::grant(
176        owner,
177        &mut definition.execute_acl,
178        grantee,
179        grantor,
180        grant_option,
181    );
182    Ok(())
183}
184
185/// Store the default ACL explicitly, as every GRANT and REVOKE does.
186pub fn make_routine_acl_explicit(definition: &mut CreateFunction) -> Result<(), SQLError> {
187    let owner = bound_routine_owner(definition)?;
188    definition.execute_acl = object_acl::explicit_acl(owner, definition.execute_acl.take());
189    Ok(())
190}
191
192pub fn revoke_routine_acl(
193    definition: &mut CreateFunction,
194    grantee: Option<RoleIdentity>,
195    grantor: RoleIdentity,
196    grant_option_only: bool,
197    cascade: bool,
198) -> Result<(), SQLError> {
199    let owner = bound_routine_owner(definition)?;
200    object_acl::revoke(
201        owner,
202        &mut definition.execute_acl,
203        grantee,
204        grantor,
205        grant_option_only,
206        cascade,
207    )
208}
209
210pub fn rewrite_routine_acl_owner(
211    definition: &mut CreateFunction,
212    old_owner: RoleIdentity,
213    new_owner: RoleIdentity,
214) {
215    object_acl::rewrite_owner(&mut definition.execute_acl, old_owner, new_owner);
216}
217
218pub fn routine_acl_warning(is_grant: bool, name: &str) -> crate::SQLNotice {
219    object_acl::acl_warning(is_grant, name.rsplit('.').next().unwrap_or(name))
220}