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