1use super::{
10 guards::RoleCatalogGuards,
11 memberships::{
12 apply_grant_role_statement, insert_membership, insufficient_privilege,
13 require_role_attribute_authority, role_has_admin, role_is_superuser,
14 },
15 resolve_role_reference, RoleDefinition, RoleMembership, RoleMembershipKey, RoleReferenceNames,
16};
17use crate::{
18 ast::{
19 AlterRoleStmt, CreateRoleStmt, DropRoleStmt, GrantRoleStmt, RoleAttribute,
20 RoleMembershipOptions,
21 },
22 SQLError,
23};
24use std::collections::{BTreeMap, BTreeSet};
25
26pub trait RoleNotices {
27 fn notice(&self, level: &str, message: &str);
28}
29
30#[derive(Clone, Copy)]
31pub struct RoleValidationContext<'a> {
32 pub names: &'a dyn RoleReferenceNames,
33 pub roles: &'a dyn RoleCatalogGuards,
34 pub notices: &'a dyn RoleNotices,
35}
36
37pub fn require_role_creation(context: &RoleValidationContext<'_>) -> Result<(), SQLError> {
38 let current = context.names.current_user_name();
39 let allowed = context
40 .roles
41 .role_definitions()
42 .get(¤t)
43 .is_some_and(|role| {
44 role.has(RoleAttribute::Superuser) || role.has(RoleAttribute::CreateRole)
45 });
46 if allowed {
47 Ok(())
48 } else {
49 Err(insufficient_privilege("permission denied to create role"))
50 }
51}
52
53pub fn require_role_administration_for(
54 catalog: &dyn RoleCatalogGuards,
55 roles: &BTreeMap<String, RoleDefinition>,
56 current: &str,
57 target: &str,
58 action: &str,
59) -> Result<(), SQLError> {
60 if role_is_superuser(roles, current) {
61 return Ok(());
62 }
63 let can_create_roles = roles
64 .get(current)
65 .is_some_and(|role| role.has(RoleAttribute::CreateRole));
66 let memberships = catalog.role_memberships();
67 if can_create_roles && role_has_admin(&memberships, current, target) {
68 Ok(())
69 } else {
70 Err(insufficient_privilege(&format!(
71 "permission denied to {action}"
72 )))
73 }
74}
75
76pub fn create_role_candidate(
77 roles: &BTreeMap<String, RoleDefinition>,
78 current: &str,
79 statement: &CreateRoleStmt,
80) -> Result<(BTreeMap<String, RoleDefinition>, bool), SQLError> {
81 if roles.contains_key(&statement.name) {
82 return Err(SQLError::Routine {
83 sqlstate: "42710".into(),
84 message: format!("role \"{}\" already exists", statement.name),
85 });
86 }
87 let current_is_superuser = role_is_superuser(roles, current);
88 let mut next_roles = roles.clone();
89 next_roles.insert(
90 statement.name.clone(),
91 RoleDefinition::from_create(statement),
92 );
93 Ok((next_roles, current_is_superuser))
94}
95
96pub fn apply_create_role_memberships(
97 context: &RoleValidationContext<'_>,
98 statement: &CreateRoleStmt,
99 current: &str,
100 current_is_superuser: bool,
101 roles: &BTreeMap<String, RoleDefinition>,
102 memberships: &mut BTreeMap<RoleMembershipKey, RoleMembership>,
103) -> Result<(), SQLError> {
104 if !current_is_superuser {
105 let bootstrap = roles
106 .values()
107 .find(|role| role.has(RoleAttribute::Superuser))
108 .map(|role| role.name.clone())
109 .ok_or_else(|| SQLError::Internal("role catalog has no bootstrap superuser".into()))?;
110 insert_membership(
111 memberships,
112 &statement.name,
113 current,
114 &bootstrap,
115 RoleMembershipOptions {
116 admin: Some(true),
117 inherit: Some(false),
118 set: Some(false),
119 },
120 roles,
121 );
122 }
123 let in_roles = statement
124 .in_roles
125 .iter()
126 .map(|role| resolve_role_reference(context.names, role))
127 .collect::<Vec<_>>();
128 if !in_roles.is_empty() {
129 apply_grant_role_statement(
130 roles,
131 memberships,
132 current,
133 &GrantRoleStmt {
134 granted_roles: in_roles,
135 grantee_roles: vec![statement.name.clone()],
136 is_grant: true,
137 options: RoleMembershipOptions::default(),
138 grantor: None,
139 cascade: false,
140 },
141 )?;
142 }
143 let role_members = statement
144 .role_members
145 .iter()
146 .map(|role| resolve_role_reference(context.names, role))
147 .collect::<Vec<_>>();
148 if !role_members.is_empty() {
149 apply_grant_role_statement(
150 roles,
151 memberships,
152 current,
153 &GrantRoleStmt {
154 granted_roles: vec![statement.name.clone()],
155 grantee_roles: role_members,
156 is_grant: true,
157 options: RoleMembershipOptions::default(),
158 grantor: None,
159 cascade: false,
160 },
161 )?;
162 }
163 let admin_members = statement
164 .admin_members
165 .iter()
166 .map(|role| resolve_role_reference(context.names, role))
167 .collect::<Vec<_>>();
168 if !admin_members.is_empty() {
169 apply_grant_role_statement(
170 roles,
171 memberships,
172 current,
173 &GrantRoleStmt {
174 granted_roles: vec![statement.name.clone()],
175 grantee_roles: admin_members,
176 is_grant: true,
177 options: RoleMembershipOptions {
178 admin: Some(true),
179 ..RoleMembershipOptions::default()
180 },
181 grantor: None,
182 cascade: false,
183 },
184 )?;
185 }
186 Ok(())
187}
188
189pub fn alter_role_candidate(
190 context: &RoleValidationContext<'_>,
191 roles: &BTreeMap<String, RoleDefinition>,
192 current: &str,
193 name: String,
194 statement: &AlterRoleStmt,
195) -> Result<BTreeMap<String, RoleDefinition>, SQLError> {
196 let existing = roles.get(&name).cloned().ok_or_else(|| SQLError::Routine {
197 sqlstate: "42704".into(),
198 message: format!("role \"{name}\" does not exist"),
199 })?;
200 require_role_administration_for(context.roles, roles, current, &name, "alter role")?;
201 require_role_attribute_authority(
202 roles,
203 current,
204 statement.attributes.keys().copied(),
205 "alter role",
206 )?;
207 let current_is_superuser = roles
208 .get(current)
209 .is_some_and(|role| role.has(RoleAttribute::Superuser));
210 if (statement.attributes.contains_key(&RoleAttribute::Superuser)
211 || existing.has(RoleAttribute::Superuser))
212 && !current_is_superuser
213 {
214 return Err(insufficient_privilege(
215 "must be superuser to alter superuser roles or change superuser attribute",
216 ));
217 }
218 let mut updated = existing;
219 for (&attribute, &enabled) in &statement.attributes {
220 if enabled {
221 updated.attributes.insert(attribute);
222 } else {
223 updated.attributes.remove(&attribute);
224 }
225 }
226 if let Some(value) = statement.connection_limit {
227 updated.connection_limit = value;
228 }
229 let mut next = roles.clone();
230 next.insert(name, updated);
231 Ok(next)
232}
233
234pub fn resolve_drop_role_names(
235 context: &RoleValidationContext<'_>,
236 statement: &DropRoleStmt,
237 current: &str,
238 session: &str,
239 snapshot: &BTreeMap<String, RoleDefinition>,
240) -> Result<Vec<String>, SQLError> {
241 let mut names = Vec::new();
242 for requested in &statement.names {
243 let name = resolve_role_reference(context.names, requested);
244 if !snapshot.contains_key(&name) {
245 if statement.if_exists {
246 context.notices.notice(
247 "NOTICE",
248 &format!("role \"{name}\" does not exist, skipping"),
249 );
250 continue;
251 }
252 return Err(SQLError::Routine {
253 sqlstate: "42704".into(),
254 message: format!("role \"{name}\" does not exist"),
255 });
256 }
257 if name == current || name == session {
258 return Err(SQLError::Routine {
259 sqlstate: "55006".into(),
260 message: "current user cannot be dropped".into(),
261 });
262 }
263 require_role_administration_for(context.roles, snapshot, current, &name, "drop role")?;
264 names.push(name);
265 }
266 Ok(names)
267}
268
269pub fn bind_grant_role_statement(
270 context: &RoleValidationContext<'_>,
271 statement: &GrantRoleStmt,
272) -> GrantRoleStmt {
273 GrantRoleStmt {
274 granted_roles: statement
275 .granted_roles
276 .iter()
277 .map(|role| resolve_role_reference(context.names, role))
278 .collect(),
279 grantee_roles: statement
280 .grantee_roles
281 .iter()
282 .map(|role| resolve_role_reference(context.names, role))
283 .collect(),
284 is_grant: statement.is_grant,
285 options: statement.options,
286 grantor: statement
287 .grantor
288 .as_ref()
289 .map(|role| resolve_role_reference(context.names, role)),
290 cascade: statement.cascade,
291 }
292}
293
294pub fn ensure_no_grantor_dependencies(
295 memberships: &BTreeMap<RoleMembershipKey, RoleMembership>,
296 names_set: &BTreeSet<String>,
297) -> Result<(), SQLError> {
298 for membership in memberships.values() {
299 if names_set.contains(&membership.grantor)
300 && !names_set.contains(&membership.role)
301 && !names_set.contains(&membership.member)
302 {
303 return Err(SQLError::Routine {
304 sqlstate: "2BP01".into(),
305 message: format!(
306 "role \"{}\" cannot be dropped because some objects depend on it: privileges for membership of role {} in role {}",
307 membership.grantor, membership.member, membership.role
308 ),
309 });
310 }
311 }
312 Ok(())
313}
314
315#[cfg(test)]
316mod tests;