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(crate::SQLNotice::notice(self.membership_notice(
178 roles,
179 member,
180 "has already been granted",
181 )?))
182 } else {
183 MembershipChange::Update(vec![MembershipUpdate {
184 before: before.clone(),
185 after: Some(after),
186 }])
187 });
188 }
189 let inherit = match self.options.inherit {
190 Some(value) => value,
191 None => member
192 .role_definition(roles)
193 .ok_or_else(|| {
194 SQLError::Internal(format!("cache lookup failed for role {}", member.oid))
195 })?
196 .has(RoleAttribute::Inherit),
197 };
198 Ok(MembershipChange::Insert(MembershipInsertion {
199 role: self.role.clone(),
200 member: member.clone(),
201 grantor: self.grantor.clone(),
202 admin_option: self.options.admin.unwrap_or(false),
203 inherit_option: inherit,
204 set_option: self.options.set.unwrap_or(true),
205 }))
206 }
207
208 fn membership_notice(
209 &self,
210 roles: &BTreeMap<String, RoleDefinition>,
211 member: &RoleBinding,
212 action: &str,
213 ) -> Result<String, SQLError> {
214 let grantor = self.grantor.require_name(roles)?;
215 Ok(format!(
216 "role \"{}\" {action} membership in role \"{}\" by role \"{grantor}\"",
217 member.name, self.role.name
218 ))
219 }
220}
221
222pub enum MembershipChange {
223 Insert(MembershipInsertion),
224 Update(Vec<MembershipUpdate>),
225 Notice(crate::SQLNotice),
227}
228
229pub struct MembershipUpdate {
230 pub before: RoleMembership,
231 pub after: Option<RoleMembership>,
232}
233
234impl MembershipUpdate {
235 pub fn apply(
236 &self,
237 memberships: &mut BTreeMap<RoleMembershipKey, RoleMembership>,
238 ) -> Result<(), SQLError> {
239 let key = self.before.key();
240 match memberships.get(&key) {
241 Some(current) if current == &self.before => {}
242 Some(_) => return Err(SQLError::Internal("tuple concurrently updated".into())),
243 None => return Err(SQLError::Internal("tuple concurrently deleted".into())),
244 }
245 if let Some(after) = &self.after {
246 memberships.insert(key, after.clone());
247 } else {
248 memberships.remove(&key);
249 }
250 Ok(())
251 }
252}
253
254pub struct MembershipInsertion {
256 pub role: RoleBinding,
257 pub member: RoleBinding,
258 pub grantor: RoleBinding,
259 pub admin_option: bool,
260 pub inherit_option: bool,
261 pub set_option: bool,
262}
263
264impl MembershipInsertion {
265 pub fn with_oid(self, oid: i64) -> Result<RoleMembership, SQLError> {
266 if oid <= 0 || oid > i64::from(u32::MAX) {
267 return Err(SQLError::Internal("invalid role membership OID".into()));
268 }
269 Ok(RoleMembership {
270 oid,
271 role: self.role,
272 member: self.member,
273 grantor: self.grantor,
274 admin_option: self.admin_option,
275 inherit_option: self.inherit_option,
276 set_option: self.set_option,
277 })
278 }
279}
280
281#[cfg(test)]
282mod tests;