Skip to main content

uqa_sql/catalog/security/
view_authorization.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! View and public-column access rules using the current authorization catalogs.
8use crate::catalog::roles::identity::RoleSubject;
9use crate::{
10    catalog::{
11        roles::guards::RoleCatalogGuards,
12        security::{
13            columns::role_has_column_privilege as column_privilege_check,
14            table::{role_has_privilege, TableAclPrivilege, TablePrivilegeCheck},
15        },
16        stored_view::StoredView,
17        view::StoredViewKind,
18    },
19    SQLError,
20};
21use uqa_core::RelationIdentity;
22pub struct ViewAuthorizationContext<'a> {
23    pub roles: &'a dyn RoleCatalogGuards,
24}
25impl ViewAuthorizationContext<'_> {
26    pub fn ensure_view_privilege_for(
27        &self,
28        name: &str,
29        view: &StoredView,
30        subject: &(impl RoleSubject + ?Sized),
31        privilege: TableAclPrivilege,
32    ) -> Result<(), SQLError> {
33        let relation = RelationIdentity::from_legacy_name(name).map_err(SQLError::Internal)?;
34        let security = view.security();
35        let roles = self.roles.role_definitions();
36        let memberships = self.roles.role_memberships();
37        let security = security.resolve(&roles).map_err(SQLError::Internal)?;
38        if role_has_privilege(
39            &security,
40            subject,
41            TablePrivilegeCheck {
42                privilege,
43                grant_option: false,
44            },
45            &roles,
46            &memberships,
47        ) {
48            return Ok(());
49        }
50        Err(SQLError::Routine {
51            sqlstate: "42501".into(),
52            message: format!(
53                "permission denied for {} {}",
54                match view.kind {
55                    StoredViewKind::View => "view",
56                    StoredViewKind::Materialized => "materialized view",
57                },
58                relation.name
59            ),
60        })
61    }
62    pub fn ensure_view_column_privilege_for(
63        &self,
64        name: &str,
65        view: &StoredView,
66        column: &str,
67        subject: &(impl RoleSubject + ?Sized),
68        privilege: TableAclPrivilege,
69    ) -> Result<(), SQLError> {
70        let relation = RelationIdentity::from_legacy_name(name).map_err(SQLError::Internal)?;
71        let security = view.security();
72        let roles = self.roles.role_definitions();
73        let memberships = self.roles.role_memberships();
74        let security = security.resolve(&roles).map_err(SQLError::Internal)?;
75        if column_privilege_check(
76            &security,
77            column,
78            subject,
79            TablePrivilegeCheck {
80                privilege,
81                grant_option: false,
82            },
83            &roles,
84            &memberships,
85        ) {
86            return Ok(());
87        }
88        Err(SQLError::Routine {
89            sqlstate: "42501".into(),
90            message: format!(
91                "permission denied for {} {}",
92                match view.kind {
93                    StoredViewKind::View => "view",
94                    StoredViewKind::Materialized => "materialized view",
95                },
96                relation.name
97            ),
98        })
99    }
100    pub fn ensure_any_view_column_privilege_for(
101        &self,
102        name: &str,
103        view: &StoredView,
104        subject: &(impl RoleSubject + ?Sized),
105        privilege: TableAclPrivilege,
106    ) -> Result<(), SQLError> {
107        let security = view.security();
108        let roles = self.roles.role_definitions();
109        let memberships = self.roles.role_memberships();
110        let security = security.resolve(&roles).map_err(SQLError::Internal)?;
111        let check = TablePrivilegeCheck {
112            privilege,
113            grant_option: false,
114        };
115        let columns = view.output_columns.as_deref().ok_or_else(|| {
116            SQLError::Internal(format!(
117                "loaded view `{name}` has no durable public column metadata"
118            ))
119        })?;
120        if role_has_privilege(&security, subject, check, &roles, &memberships)
121            || columns.iter().any(|column| {
122                column_privilege_check(&security, column, subject, check, &roles, &memberships)
123            })
124        {
125            return Ok(());
126        }
127        drop(memberships);
128        drop(roles);
129        self.ensure_view_privilege_for(name, view, subject, privilege)
130    }
131}
132
133#[cfg(test)]
134mod tests;