Skip to main content

uqa_sql/catalog/security/
sequence_grants.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Sequence GRANT target binding, namespace rules and ACL candidates.
8
9use super::{
10    sequence::{grant_acl, revoke_acl, select_acl_grantor, AclPrivilege},
11    sequence_inquiry::SequencePrivilegeResolution,
12    SequenceSecurity,
13};
14use crate::catalog::roles::identity::RoleSubject;
15use crate::{
16    ast::{GrantSequenceStmt, SequenceRevokeBehavior},
17    catalog::{
18        resolution::RelationResolution,
19        roles::{RoleDefinition, RoleMembership, RoleMembershipKey},
20    },
21    SQLError,
22};
23use std::collections::BTreeMap;
24use uqa_core::catalog_acl::AclGrantee;
25use uqa_core::RelationIdentity;
26
27pub use super::grants::{
28    bind_grant_schemas as bind_sequence_grant_schemas, GrantNamespace as SequenceGrantNamespace,
29};
30
31pub struct ResolvedSequenceGrantTarget {
32    pub requested: String,
33    pub name: String,
34    pub relation: RelationIdentity,
35    pub kind: &'static str,
36}
37
38pub fn bind_named_sequence_grants(
39    resolution: &dyn SequencePrivilegeResolution,
40    names: &[String],
41) -> Result<Vec<ResolvedSequenceGrantTarget>, SQLError> {
42    let mut resolved = Vec::with_capacity(names.len());
43    for requested in names {
44        let (name, kind) = match resolution.visible_relation_kind(requested)? {
45            RelationResolution::Found(name, kind) => (name, kind),
46            RelationResolution::MissingSchema(schema) => {
47                return Err(SQLError::Routine {
48                    sqlstate: "3F000".into(),
49                    message: format!("schema \"{schema}\" does not exist"),
50                });
51            }
52            RelationResolution::MissingRelation => {
53                return Err(SQLError::Routine {
54                    sqlstate: "42P01".into(),
55                    message: format!("relation \"{requested}\" does not exist"),
56                });
57            }
58        };
59        let relation = RelationIdentity::from_legacy_name(&name)
60            .map_err(|error| SQLError::Internal(format!("resolve sequence `{name}`: {error}")))?;
61        resolved.push(ResolvedSequenceGrantTarget {
62            requested: requested.clone(),
63            name,
64            relation,
65            kind,
66        });
67    }
68    Ok(resolved)
69}
70
71pub fn sequence_grants_in_schemas<'a>(
72    resolved_schemas: &[String],
73    sequences: impl Iterator<Item = &'a RelationIdentity>,
74) -> Vec<ResolvedSequenceGrantTarget> {
75    let mut targets = sequences
76        .filter(|relation| resolved_schemas.contains(&relation.schema))
77        .map(|relation| ResolvedSequenceGrantTarget {
78            requested: relation.qualified_name(),
79            name: relation.qualified_name(),
80            relation: relation.clone(),
81            kind: "sequence",
82        })
83        .collect::<Vec<_>>();
84    targets.sort_by(|left, right| left.relation.cmp(&right.relation));
85    targets
86}
87
88pub fn apply_sequence_acl(
89    statement: &GrantSequenceStmt,
90    grantees: &[AclGrantee],
91    privileges: &[AclPrivilege],
92    current_user: &(impl RoleSubject + ?Sized),
93    roles: &BTreeMap<String, RoleDefinition>,
94    memberships: &BTreeMap<RoleMembershipKey, RoleMembership>,
95    current: &SequenceSecurity,
96) -> Result<(SequenceSecurity, usize), SQLError> {
97    let grantors = privileges
98        .iter()
99        .map(|privilege| {
100            (
101                *privilege,
102                select_acl_grantor(current, *privilege, current_user, roles, memberships),
103            )
104        })
105        .collect::<Vec<_>>();
106    let grantable = grantors
107        .iter()
108        .filter(|(_, grantor)| grantor.is_some())
109        .count();
110    let mut next = current.clone();
111    for (privilege, grantor) in grantors {
112        let Some(grantor) = grantor else {
113            continue;
114        };
115        if statement.is_grant {
116            grant_acl(
117                &mut next,
118                privilege,
119                grantees,
120                &grantor,
121                statement.grant_option,
122            );
123        } else {
124            revoke_acl(
125                &mut next,
126                privilege,
127                grantees,
128                &grantor,
129                statement.grant_option_only,
130                statement.revoke_behavior == SequenceRevokeBehavior::Cascade,
131            )?;
132        }
133    }
134    Ok((next, grantable))
135}
136
137pub fn validate_sequence_acl_roles(
138    statement: &GrantSequenceStmt,
139    grantees: &[AclGrantee],
140    requested_grantor: Option<&str>,
141    current_user: &(impl RoleSubject + ?Sized),
142    roles: &BTreeMap<String, RoleDefinition>,
143) -> Result<(), SQLError> {
144    for role in grantees {
145        if role
146            .role_name()
147            .is_some_and(|name| !roles.contains_key(name))
148        {
149            return Err(SQLError::Routine {
150                sqlstate: "42704".into(),
151                message: format!("role \"{role}\" does not exist"),
152            });
153        }
154    }
155    if statement.is_grant && statement.grant_option && grantees.iter().any(AclGrantee::is_public) {
156        return Err(SQLError::Routine {
157            sqlstate: "0LP01".into(),
158            message: "grant options can only be granted to roles".into(),
159        });
160    }
161    if let Some(requested_grantor) = requested_grantor {
162        if !roles.contains_key(requested_grantor) {
163            return Err(SQLError::Routine {
164                sqlstate: "42704".into(),
165                message: format!("role \"{requested_grantor}\" does not exist"),
166            });
167        }
168        if current_user.role_name(roles) != Some(requested_grantor) {
169            return Err(SQLError::Routine {
170                sqlstate: "0A000".into(),
171                message: "grantor must be current user".into(),
172            });
173        }
174    }
175    Ok(())
176}
177
178pub fn validate_sequence_grant_target_kinds(
179    targets: &[ResolvedSequenceGrantTarget],
180) -> Result<(), SQLError> {
181    for target in targets {
182        if target.kind == "sequence" {
183            continue;
184        }
185        return Err(SQLError::Routine {
186            sqlstate: "42809".into(),
187            message: format!("\"{}\" is not a sequence", target.requested),
188        });
189    }
190    Ok(())
191}
192
193pub fn sequence_acl_warning(is_grant: bool, partial: bool, name: &str) -> (&'static str, String) {
194    let message = match (is_grant, partial) {
195        (true, true) => format!("not all privileges were granted for \"{name}\""),
196        (true, false) => format!("no privileges were granted for \"{name}\""),
197        (false, true) => format!("not all privileges could be revoked for \"{name}\""),
198        (false, false) => format!("no privileges could be revoked for \"{name}\""),
199    };
200    ("WARNING", message)
201}