uqa_sql/catalog/security/
view_ownership.rs1use crate::{
9 catalog::{
10 roles::{guards::RoleCatalogGuards, role_inherits, RoleReferenceNames},
11 security::table::{role_has_table_privilege, TableAclPrivilege},
12 stored_view::StoredView,
13 view::StoredViewKind,
14 },
15 SQLError,
16};
17use uqa_core::RelationIdentity;
18
19pub use super::ownership::RelationOwnerSchemas as ViewOwnerSchemas;
20#[derive(Clone, Copy)]
21pub struct ViewOwnershipContext<'a> {
22 pub session: &'a dyn RoleReferenceNames,
23 pub roles: &'a dyn RoleCatalogGuards,
24 pub schemas: &'a dyn ViewOwnerSchemas,
25}
26fn current_user_has_role_privileges(context: ViewOwnershipContext<'_>, target: &str) -> bool {
27 let current = context.session.current_user_name();
28 let roles = context.roles.role_definitions();
29 let memberships = context.roles.role_memberships();
30 role_inherits(&roles, &memberships, ¤t, target)
31}
32
33fn view_kind_name(view: &StoredView) -> &'static str {
34 match view.kind {
35 StoredViewKind::View => "view",
36 StoredViewKind::Materialized => "materialized view",
37 }
38}
39
40pub fn ensure_view_owner(
41 context: ViewOwnershipContext<'_>,
42 canonical_name: &str,
43 view: &StoredView,
44) -> Result<String, SQLError> {
45 if current_user_has_role_privileges(context, &view.role_owner) {
46 return Ok(view.role_owner.clone());
47 }
48 let relation = RelationIdentity::from_legacy_name(canonical_name)
49 .map_err(|error| SQLError::Internal(format!("resolve view `{canonical_name}`: {error}")))?;
50 Err(SQLError::Routine {
51 sqlstate: "42501".into(),
52 message: format!(
53 "must be owner of {} {}",
54 view_kind_name(view),
55 relation.name
56 ),
57 })
58}
59
60pub fn ensure_view_drop_authority(
61 context: ViewOwnershipContext<'_>,
62 canonical_name: &str,
63 view: &StoredView,
64) -> Result<(), SQLError> {
65 let relation = RelationIdentity::from_legacy_name(canonical_name)
66 .map_err(|error| SQLError::Internal(format!("resolve view `{canonical_name}`: {error}")))?;
67 if current_user_has_role_privileges(context, &view.role_owner)
68 || context
69 .schemas
70 .schema_security(&relation.schema)
71 .is_some_and(|security| current_user_has_role_privileges(context, &security.role_owner))
72 {
73 return Ok(());
74 }
75 Err(SQLError::Routine {
76 sqlstate: "42501".into(),
77 message: format!(
78 "must be owner of {} {}",
79 view_kind_name(view),
80 relation.name
81 ),
82 })
83}
84
85pub fn ensure_materialized_view_maintenance(
86 context: ViewOwnershipContext<'_>,
87 canonical_name: &str,
88 view: &StoredView,
89) -> Result<(), SQLError> {
90 debug_assert_eq!(view.kind, StoredViewKind::Materialized);
91 let current_user = context.session.current_user_name();
92 let roles = context.roles.role_definitions();
93 let memberships = context.roles.role_memberships();
94 if role_has_table_privilege(
95 &view.security(),
96 ¤t_user,
97 TableAclPrivilege::Maintain,
98 &roles,
99 &memberships,
100 ) {
101 return Ok(());
102 }
103 let relation = RelationIdentity::from_legacy_name(canonical_name).map_err(|error| {
104 SQLError::Internal(format!(
105 "resolve materialized view `{canonical_name}`: {error}"
106 ))
107 })?;
108 Err(SQLError::Routine {
109 sqlstate: "42501".into(),
110 message: format!("permission denied for materialized view {}", relation.name),
111 })
112}
113#[cfg(test)]
114mod tests;