uqa_sql/catalog/roles/memberships/
command.rs1use super::{
10 grants, RoleAttribute, RoleBinding, RoleDefinition, RoleMembership, RoleMembershipKey,
11 RoleSubject, SQLError,
12};
13use crate::{
14 ast::{AlterRoleStmt, GrantRoleStmt, RoleMembershipAction, RoleMembershipOptions},
15 catalog::roles::{resolve_role_specification, RoleReference, RoleReferenceNames},
16};
17use std::collections::BTreeMap;
18
19mod revoke;
20pub use revoke::MembershipRevocation;
21
22pub fn creator_membership(
23 roles: &BTreeMap<String, RoleDefinition>,
24 current: &(impl RoleSubject + ?Sized),
25 created: &RoleDefinition,
26) -> Result<MembershipInsertion, SQLError> {
27 let creator = current
28 .role_definition(roles)
29 .ok_or_else(|| super::insufficient_privilege("permission denied to create role"))?;
30 let bootstrap = roles
31 .values()
32 .find(|role| role.oid == 10)
33 .ok_or_else(|| SQLError::Internal("role catalog has no bootstrap superuser".into()))?;
34 Ok(MembershipInsertion {
35 role: RoleBinding::from_definition(created)?,
36 member: RoleBinding::from_definition(creator)?,
37 grantor: RoleBinding::from_definition(bootstrap)?,
38 admin_option: true,
39 inherit_option: false,
40 set_option: false,
41 })
42}
43
44#[derive(Clone)]
45pub struct MembershipRecipients {
46 pub members: Vec<RoleBinding>,
47 pub grantor: Option<RoleBinding>,
48}
49
50impl MembershipRecipients {
51 pub fn bind(
53 names: &dyn RoleReferenceNames,
54 roles: &BTreeMap<String, RoleDefinition>,
55 statement: &GrantRoleStmt,
56 ) -> Result<Self, SQLError> {
57 let grantor = statement
58 .grantor
59 .as_ref()
60 .map(|role| resolve_role_specification(names, role).bind(roles))
61 .transpose()?;
62 let members = statement
63 .grantee_roles
64 .iter()
65 .map(|role| resolve_role_specification(names, role).bind(roles))
66 .collect::<Result<_, _>>()?;
67 Ok(Self { members, grantor })
68 }
69}
70
71#[derive(Clone)]
72pub struct MembershipTarget {
73 pub role: RoleBinding,
74 pub grantor: RoleBinding,
75 pub members: Vec<RoleBinding>,
76 pub is_grant: bool,
77 pub options: RoleMembershipOptions,
78 pub cascade: bool,
79}
80
81impl MembershipTarget {
82 pub fn authorize_group(
84 names: &dyn RoleReferenceNames,
85 roles: &BTreeMap<String, RoleDefinition>,
86 memberships: &BTreeMap<RoleMembershipKey, RoleMembership>,
87 current: &(impl RoleSubject + ?Sized),
88 statement: &AlterRoleStmt,
89 ) -> Result<Self, SQLError> {
90 let target = resolve_role_specification(names, &statement.name);
91 let role = target.bind(roles)?;
92 grants::require_group_authority(roles, memberships, current, &role)?;
93 let command = GrantRoleStmt {
94 granted_roles: Vec::new(),
95 grantee_roles: statement.members.clone(),
96 is_grant: statement.membership_action == Some(RoleMembershipAction::Add),
97 options: RoleMembershipOptions::default(),
98 grantor: None,
99 cascade: false,
100 };
101 let recipients = MembershipRecipients::bind(names, roles, &command)?;
102 Self::authorize(roles, memberships, current, &target, &recipients, &command)
103 }
104
105 pub fn authorize(
107 roles: &BTreeMap<String, RoleDefinition>,
108 memberships: &BTreeMap<RoleMembershipKey, RoleMembership>,
109 current: &(impl RoleSubject + ?Sized),
110 target: &RoleReference,
111 recipients: &MembershipRecipients,
112 statement: &GrantRoleStmt,
113 ) -> Result<Self, SQLError> {
114 let role = target.bind(roles)?;
115 let grantor = grants::select_grantor(
116 roles,
117 memberships,
118 current,
119 &role,
120 recipients.grantor.as_ref(),
121 statement.is_grant,
122 )?;
123 Ok(Self {
124 role,
125 grantor,
126 members: recipients.members.clone(),
127 is_grant: statement.is_grant,
128 options: statement.options,
129 cascade: statement.cascade,
130 })
131 }
132
133 pub fn validate_graph(
134 &self,
135 memberships: &BTreeMap<RoleMembershipKey, RoleMembership>,
136 ) -> Result<(), SQLError> {
137 if self.is_grant {
138 grants::validate_grant(
139 memberships,
140 &self.role,
141 &self.grantor,
142 &self.members,
143 self.options,
144 )?;
145 }
146 Ok(())
147 }
148
149 pub fn change_for_member(
150 &self,
151 roles: &BTreeMap<String, RoleDefinition>,
152 memberships: &BTreeMap<RoleMembershipKey, RoleMembership>,
153 member: &RoleBinding,
154 ) -> Result<MembershipChange, SQLError> {
155 let key = RoleMembershipKey {
156 role: self.role.identity(),
157 member: member.identity(),
158 grantor: self.grantor.identity(),
159 };
160 if !self.is_grant {
161 return Err(SQLError::Internal(
162 "role revocation requires a complete target plan".into(),
163 ));
164 }
165 if let Some(before) = memberships.get(&key) {
166 let mut after = before.clone();
167 if let Some(option) = self.options.admin {
168 after.admin_option = option;
169 }
170 if let Some(option) = self.options.inherit {
171 after.inherit_option = option;
172 }
173 if let Some(option) = self.options.set {
174 after.set_option = option;
175 }
176 return Ok(if after == *before {
177 MembershipChange::Notice {
178 level: "NOTICE",
179 message: self.membership_notice(roles, member, "has already been granted")?,
180 }
181 } else {
182 MembershipChange::Update(vec![MembershipUpdate {
183 before: before.clone(),
184 after: Some(after),
185 }])
186 });
187 }
188 let inherit = match self.options.inherit {
189 Some(value) => value,
190 None => member
191 .role_definition(roles)
192 .ok_or_else(|| {
193 SQLError::Internal(format!("cache lookup failed for role {}", member.oid))
194 })?
195 .has(RoleAttribute::Inherit),
196 };
197 Ok(MembershipChange::Insert(MembershipInsertion {
198 role: self.role.clone(),
199 member: member.clone(),
200 grantor: self.grantor.clone(),
201 admin_option: self.options.admin.unwrap_or(false),
202 inherit_option: inherit,
203 set_option: self.options.set.unwrap_or(true),
204 }))
205 }
206
207 fn membership_notice(
208 &self,
209 roles: &BTreeMap<String, RoleDefinition>,
210 member: &RoleBinding,
211 action: &str,
212 ) -> Result<String, SQLError> {
213 let grantor = self.grantor.require_name(roles)?;
214 Ok(format!(
215 "role \"{}\" {action} membership in role \"{}\" by role \"{grantor}\"",
216 member.name, self.role.name
217 ))
218 }
219}
220
221pub enum MembershipChange {
222 Insert(MembershipInsertion),
223 Update(Vec<MembershipUpdate>),
224 Notice {
225 level: &'static str,
226 message: String,
227 },
228}
229
230pub struct MembershipUpdate {
231 pub before: RoleMembership,
232 pub after: Option<RoleMembership>,
233}
234
235impl MembershipUpdate {
236 pub fn apply(
237 &self,
238 memberships: &mut BTreeMap<RoleMembershipKey, RoleMembership>,
239 ) -> Result<(), SQLError> {
240 let key = self.before.key();
241 match memberships.get(&key) {
242 Some(current) if current == &self.before => {}
243 Some(_) => return Err(SQLError::Internal("tuple concurrently updated".into())),
244 None => return Err(SQLError::Internal("tuple concurrently deleted".into())),
245 }
246 if let Some(after) = &self.after {
247 memberships.insert(key, after.clone());
248 } else {
249 memberships.remove(&key);
250 }
251 Ok(())
252 }
253}
254
255pub struct MembershipInsertion {
257 pub role: RoleBinding,
258 pub member: RoleBinding,
259 pub grantor: RoleBinding,
260 pub admin_option: bool,
261 pub inherit_option: bool,
262 pub set_option: bool,
263}
264
265impl MembershipInsertion {
266 pub fn with_oid(self, oid: i64) -> Result<RoleMembership, SQLError> {
267 if oid <= 0 || oid > i64::from(u32::MAX) {
268 return Err(SQLError::Internal("invalid role membership OID".into()));
269 }
270 Ok(RoleMembership {
271 oid,
272 role: self.role,
273 member: self.member,
274 grantor: self.grantor,
275 admin_option: self.admin_option,
276 inherit_option: self.inherit_option,
277 set_option: self.set_option,
278 })
279 }
280}
281
282#[cfg(test)]
283mod tests;