uqa_sql/catalog/security/
ownership.rs1use super::BoundSchemaSecurity;
9use crate::catalog::roles::identity::RoleSubject;
10use crate::{
11 ast::RoleAttribute,
12 catalog::roles::{
13 require_set_role, role_inherits, RoleDefinition, RoleMembership, RoleMembershipKey,
14 },
15 SQLError,
16};
17use std::collections::BTreeMap;
18
19pub trait RelationOwnerSchemas {
20 fn schema_security(&self, schema: &str) -> Option<BoundSchemaSecurity>;
21}
22
23pub fn require_relation_ownership(
24 name: &str,
25 kind: &str,
26 has_owner_privileges: bool,
27) -> Result<(), SQLError> {
28 if has_owner_privileges {
29 Ok(())
30 } else {
31 Err(SQLError::Routine {
32 sqlstate: "42501".into(),
33 message: format!("must be owner of {kind} {name}"),
34 })
35 }
36}
37
38pub fn reject_system_relation_alter(relation: &uqa_core::RelationIdentity) -> Result<(), SQLError> {
40 let pinned = crate::catalog::SystemRelation::at(&relation.schema, &relation.name)
41 .is_some_and(|system| system.oid() < 12_000);
42 if pinned || relation.schema == "pg_toast" {
43 Err(SQLError::Routine {
44 sqlstate: "42501".into(),
45 message: format!(
46 "permission denied: \"{}\" is a system catalog",
47 relation.name
48 ),
49 })
50 } else {
51 Ok(())
52 }
53}
54
55#[cfg(test)]
56mod tests;
57
58pub struct OwnerChangeAuthority<'a> {
59 pub roles: &'a BTreeMap<String, RoleDefinition>,
60 pub memberships: &'a BTreeMap<RoleMembershipKey, RoleMembership>,
61 pub current_user: &'a dyn RoleSubject,
62 pub new_owner: &'a str,
63}
64
65impl OwnerChangeAuthority<'_> {
66 pub fn is_superuser(&self) -> bool {
67 self.current_user
68 .role_definition(self.roles)
69 .is_some_and(|role| role.has(RoleAttribute::Superuser))
70 }
71
72 pub fn require_owner_change(
73 &self,
74 owner: &str,
75 kind: &str,
76 name: &str,
77 ) -> Result<(), SQLError> {
78 require_relation_ownership(
79 name,
80 kind,
81 role_inherits(self.roles, self.memberships, self.current_user, owner),
82 )?;
83 require_set_role(
84 self.roles,
85 self.memberships,
86 self.current_user,
87 self.new_owner,
88 )
89 }
90
91 pub fn require_schema_create(
92 &self,
93 schemas: &dyn RelationOwnerSchemas,
94 schema: &str,
95 ) -> Result<(), SQLError> {
96 if self.is_superuser() {
97 return Ok(());
98 }
99 let security = schemas
100 .schema_security(schema)
101 .ok_or_else(|| SQLError::Routine {
102 sqlstate: "3F000".into(),
103 message: format!("schema \"{schema}\" does not exist"),
104 })?;
105 if super::schema::role_has_schema_privilege(
106 &security.resolve(self.roles).map_err(SQLError::Internal)?,
107 self.new_owner,
108 super::schema::SchemaAclPrivilege::Create,
109 self.roles,
110 self.memberships,
111 ) {
112 Ok(())
113 } else {
114 Err(SQLError::Routine {
115 sqlstate: "42501".into(),
116 message: format!("permission denied for schema {schema}"),
117 })
118 }
119 }
120
121 pub fn require_database_create(
122 &self,
123 security: &super::database::BoundDatabaseSecurity,
124 ) -> Result<(), SQLError> {
125 if super::database::role_has_database_privilege(
126 &security.resolve(self.roles).map_err(SQLError::Internal)?,
127 self.current_user,
128 super::database::DatabaseAclPrivilege::Create,
129 self.roles,
130 self.memberships,
131 ) {
132 Ok(())
133 } else {
134 Err(SQLError::Routine {
135 sqlstate: "42501".into(),
136 message: format!(
137 "permission denied for database {}",
138 crate::catalog::DATABASE_NAME
139 ),
140 })
141 }
142 }
143}