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