Skip to main content

uqa_sql/catalog/
roles.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Role definitions and membership semantics over explicit catalog values.
8
9use crate::ast::{CreateRoleStmt, RoleAttribute};
10use serde::{Deserialize, Serialize};
11use std::collections::BTreeSet;
12
13pub mod identity;
14pub mod rename;
15pub mod session;
16pub mod tuple;
17use identity::RoleBinding;
18pub use identity::{RoleIdentity, RoleReference};
19pub mod memberships;
20pub use memberships::{role_can_set, role_inherits};
21
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23pub struct RoleDefinition {
24    pub oid: i64,
25    /// Durable incarnation independent of the recyclable SQL-visible OID. Zero identifies legacy metadata that requires initial-open migration.
26    #[serde(default)]
27    pub object_id: [u8; 16],
28    /// Version of this definition tuple; even an attribute assignment of the same value creates a new tuple version. Zero requires initial-open conversion.
29    #[serde(default)]
30    pub revision: u64,
31    pub name: String,
32    pub attributes: BTreeSet<RoleAttribute>,
33    pub connection_limit: i32,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
37pub struct RoleMembershipKey {
38    pub role: RoleIdentity,
39    pub member: RoleIdentity,
40    pub grantor: RoleIdentity,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
44pub struct RoleMembership {
45    pub oid: i64,
46    pub role: RoleBinding,
47    pub member: RoleBinding,
48    pub grantor: RoleBinding,
49    pub admin_option: bool,
50    pub inherit_option: bool,
51    pub set_option: bool,
52}
53
54impl RoleMembership {
55    pub fn key(&self) -> RoleMembershipKey {
56        RoleMembershipKey {
57            role: self.role.identity(),
58            member: self.member.identity(),
59            grantor: self.grantor.identity(),
60        }
61    }
62}
63
64impl RoleDefinition {
65    pub fn identity(&self) -> RoleIdentity {
66        RoleIdentity {
67            oid: self.oid,
68            object_id: self.object_id,
69        }
70    }
71
72    pub fn bootstrap() -> Self {
73        Self {
74            oid: 10,
75            object_id: RoleIdentity::BOOTSTRAP.object_id,
76            revision: 1,
77            name: "uqa".into(),
78            attributes: BTreeSet::from([
79                RoleAttribute::Superuser,
80                RoleAttribute::Inherit,
81                RoleAttribute::CreateRole,
82                RoleAttribute::CreateDb,
83                RoleAttribute::Login,
84                RoleAttribute::BypassRls,
85            ]),
86            connection_limit: -1,
87        }
88    }
89
90    pub fn from_create(statement: &CreateRoleStmt, oid: i64, object_id: [u8; 16]) -> Self {
91        Self {
92            oid,
93            object_id,
94            revision: 1,
95            name: statement.name.clone(),
96            attributes: statement.attributes.clone(),
97            connection_limit: statement.connection_limit,
98        }
99    }
100
101    pub fn has(&self, attribute: RoleAttribute) -> bool {
102        self.attributes.contains(&attribute)
103    }
104
105    pub fn advance_revision(&mut self) -> Result<(), crate::SQLError> {
106        self.revision = self
107            .revision
108            .checked_add(1)
109            .filter(|_| self.revision != 0)
110            .ok_or_else(|| {
111                crate::SQLError::Internal("invalid or exhausted role tuple revision".into())
112            })?;
113        Ok(())
114    }
115}
116
117/// Selected identities used by SQL current-user, session-user and authenticated-role references.
118pub trait RoleReferenceNames {
119    fn current_role(&self) -> RoleReference;
120    fn session_role(&self) -> RoleReference;
121    /// Session-selected role before any SECURITY DEFINER substitution.
122    fn outer_role(&self) -> RoleReference;
123    fn authenticated_role(&self) -> RoleReference {
124        self.session_role()
125    }
126}
127pub fn resolve_role_specification(
128    names: &dyn RoleReferenceNames,
129    specification: &crate::ast::RoleSpecification,
130) -> RoleReference {
131    match specification {
132        crate::ast::RoleSpecification::Named(name) => name.clone().into(),
133        crate::ast::RoleSpecification::CurrentUser => names.current_role(),
134        crate::ast::RoleSpecification::SessionUser => names.session_role(),
135    }
136}
137
138pub fn resolve_acl_role_specification(
139    names: &dyn RoleReferenceNames,
140    specification: &crate::ast::AclRoleSpecification,
141    roles: &std::collections::BTreeMap<String, RoleDefinition>,
142) -> Result<uqa_core::catalog_acl::AclGrantee, crate::SQLError> {
143    use crate::ast::AclRoleSpecification;
144    use uqa_core::catalog_acl::AclGrantee;
145    match specification {
146        AclRoleSpecification::Public => Ok(AclGrantee::Public),
147        AclRoleSpecification::Role(role) => resolve_role_specification(names, role)
148            .catalog_name(roles)
149            .map(AclGrantee::Role),
150    }
151}
152
153pub fn require_role_exists(
154    roles: &std::collections::BTreeMap<String, RoleDefinition>,
155    name: &str,
156) -> Result<(), crate::SQLError> {
157    if roles.contains_key(name) {
158        return Ok(());
159    }
160    Err(crate::SQLError::Routine {
161        sqlstate: "42704".into(),
162        message: format!("role \"{name}\" does not exist"),
163    })
164}
165pub fn require_set_role(
166    roles: &std::collections::BTreeMap<String, RoleDefinition>,
167    memberships: &std::collections::BTreeMap<RoleMembershipKey, RoleMembership>,
168    current: &(impl identity::RoleSubject + ?Sized),
169    target: &str,
170) -> Result<(), crate::SQLError> {
171    if role_can_set(roles, memberships, current, target) {
172        return Ok(());
173    }
174    Err(crate::SQLError::Routine {
175        sqlstate: "42501".into(),
176        message: format!("must be able to SET ROLE \"{target}\""),
177    })
178}
179
180pub mod definition;
181pub mod dependencies;
182pub mod guards;
183pub mod inquiry;
184pub mod restoration;