Skip to main content

uqa_sql/routines/security/
binding.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Bind legacy routine names once and validate retained owner and ACL incarnations.
8
9use super::bound_routine_owner;
10use crate::catalog::security::object_acl;
11use crate::{
12    ast::{CreateFunction, RoutineAclEntry},
13    catalog::roles::{identity::RoleSubject, RoleDefinition, RoleIdentity, RoleReference},
14    SQLError,
15};
16use serde::Deserialize;
17use std::collections::{BTreeMap, BTreeSet};
18use uqa_core::catalog_acl::AclGrantee;
19
20/// Only the initial catalog converter may interpret named routine authority.
21#[derive(Deserialize)]
22pub struct LegacyRoutineAclEntry {
23    pub role: AclGrantee,
24    #[serde(default)]
25    pub grantor: Option<String>,
26    pub grant_option: bool,
27}
28
29pub struct BoundRoutineAuthority {
30    pub owner: RoleIdentity,
31    pub execute_acl: Option<Vec<RoutineAclEntry>>,
32}
33
34pub fn bind_legacy_authority(
35    owner: &str,
36    acl: Option<Vec<LegacyRoutineAclEntry>>,
37    roles: &BTreeMap<String, RoleDefinition>,
38    implicit_owner_execute: bool,
39) -> Result<BoundRoutineAuthority, SQLError> {
40    let bind = |name: &str| {
41        RoleReference::from(name)
42            .bind(roles)
43            .map(|role| role.identity())
44    };
45    let identity = bind(owner)?;
46    let mut execute_acl = acl
47        .map(|acl| {
48            acl.into_iter()
49                .map(|entry| {
50                    Ok(RoutineAclEntry {
51                        role: entry.role.role_name().map(bind).transpose()?,
52                        grantor: bind(entry.grantor.as_deref().unwrap_or(owner))?,
53                        grant_option: entry.grant_option,
54                    })
55                })
56                .collect::<Result<Vec<_>, SQLError>>()
57        })
58        .transpose()?;
59    if implicit_owner_execute {
60        if let Some(acl) = execute_acl.as_mut() {
61            if !acl.iter().any(|entry| entry.role == Some(identity)) {
62                acl.push(RoutineAclEntry {
63                    role: Some(identity),
64                    grantor: identity,
65                    grant_option: false,
66                });
67            }
68        }
69    }
70    validate_acl(identity, execute_acl.as_deref())?;
71    Ok(BoundRoutineAuthority {
72        owner: identity,
73        execute_acl,
74    })
75}
76
77fn invalid(message: &str) -> SQLError {
78    SQLError::Internal(format!("invalid routine authority: {message}"))
79}
80
81fn validate_acl(owner: RoleIdentity, acl: Option<&[RoutineAclEntry]>) -> Result<(), SQLError> {
82    object_acl::validate_acl(owner, acl, "routine")
83}
84
85pub fn validate_routine_authority_identities(definition: &CreateFunction) -> Result<(), SQLError> {
86    validate_acl(
87        bound_routine_owner(definition)?,
88        definition.execute_acl.as_deref(),
89    )
90}
91
92fn authority_roles(definition: &CreateFunction) -> Result<BTreeSet<RoleIdentity>, SQLError> {
93    Ok(object_acl::acl_roles(
94        bound_routine_owner(definition)?,
95        definition.execute_acl.as_deref(),
96    ))
97}
98
99pub fn routine_role_dependencies(
100    definition: &CreateFunction,
101    roles: &BTreeMap<String, RoleDefinition>,
102) -> Result<BTreeSet<String>, SQLError> {
103    validate_routine_authority_identities(definition)?;
104    authority_roles(definition)?
105        .into_iter()
106        .map(|identity| {
107            identity
108                .role_name(roles)
109                .map(str::to_owned)
110                .ok_or_else(|| invalid("missing role incarnation"))
111        })
112        .collect()
113}
114
115pub fn validate_routine_authority(
116    definition: &CreateFunction,
117    roles: &BTreeMap<String, RoleDefinition>,
118) -> Result<(), SQLError> {
119    routine_role_dependencies(definition, roles).map(|_| ())
120}
121
122pub fn bind_routine_grantees(
123    grantees: &[AclGrantee],
124    roles: &BTreeMap<String, RoleDefinition>,
125) -> Result<Vec<Option<RoleIdentity>>, SQLError> {
126    object_acl::bind_grantees(grantees, roles)
127}
128
129pub fn added_routine_acl_roles(
130    before: &CreateFunction,
131    after: &CreateFunction,
132    roles: &BTreeMap<String, RoleDefinition>,
133    added: &mut BTreeSet<String>,
134) -> Result<(), SQLError> {
135    object_acl::added_acl_roles(
136        (bound_routine_owner(before)?, before.execute_acl.as_deref()),
137        (bound_routine_owner(after)?, after.execute_acl.as_deref()),
138        roles,
139        "routine",
140        added,
141    )
142}
143
144#[cfg(test)]
145mod tests;