use uqa_sql::catalog::roles::RoleReference;
use uqa_sql::{
ast::{AlterRoleStmt, CreateRoleStmt, GrantRoleStmt},
catalog::roles::{
definition::{self, require_role_creation},
memberships::require_role_attribute_authority,
resolve_role_specification, role_can_set,
},
SQLError,
};
pub mod context;
mod deletion;
mod identity;
pub use deletion::drop_roles;
mod memberships;
mod rename;
pub use rename::rename_role;
mod tuples;
use context::RoleExecutionContext;
pub fn set_role(
context: &RoleExecutionContext<'_>,
requested: Option<&str>,
) -> Result<(), SQLError> {
require_authorization_setting("role")?;
let Some(target) = requested.filter(|name| *name != "none") else {
context.publication.set_current_role(None);
return Ok(());
};
let roles = context.analysis.roles.role_definitions();
let bound = selected_role(&roles, target)?;
let session_user = context.analysis.names.session_role();
let memberships = context.analysis.roles.role_memberships();
if !role_can_set(&roles, &memberships, &session_user, target) {
return Err(SQLError::Routine {
sqlstate: "42501".into(),
message: format!("permission denied to set role \"{target}\""),
});
}
drop(memberships);
drop(roles);
context.publication.set_current_role(Some(bound));
Ok(())
}
pub fn set_session_authorization(
context: &RoleExecutionContext<'_>,
requested: Option<&str>,
) -> Result<(), SQLError> {
require_authorization_setting("session_authorization")?;
let authenticated = context.analysis.names.authenticated_role();
let roles = context.analysis.roles.role_definitions();
let original = match &authenticated {
RoleReference::Bound(identity) => identity.as_ref().clone(),
RoleReference::Named(_) => authenticated.bind(&roles)?,
};
let selected =
requested.map_or_else(|| Ok(original.clone()), |name| selected_role(&roles, name))?;
if (selected.oid != original.oid || selected.object_id != original.object_id)
&& !uqa_sql::catalog::roles::memberships::role_is_superuser(&roles, &authenticated)
{
return Err(SQLError::Routine {
sqlstate: "42501".into(),
message: "permission denied to set session authorization".into(),
});
}
drop(roles);
context.publication.set_session_authorization(selected);
Ok(())
}
fn require_authorization_setting(parameter: &str) -> Result<(), SQLError> {
if crate::routines::invocation::scopes::security_definer_active() {
return Err(SQLError::Routine {
sqlstate: "42501".into(),
message: format!(
"cannot set parameter \"{parameter}\" within security-definer function"
),
});
}
Ok(())
}
fn selected_role(
roles: &std::collections::BTreeMap<String, uqa_sql::catalog::roles::RoleDefinition>,
name: &str,
) -> Result<uqa_sql::catalog::roles::identity::RoleBinding, SQLError> {
let role = roles.get(name).ok_or_else(|| SQLError::Routine {
sqlstate: "22023".into(),
message: format!("role \"{name}\" does not exist"),
})?;
uqa_sql::catalog::roles::identity::RoleBinding::from_definition(role)
}
pub fn create_role(
context: &RoleExecutionContext<'_>,
statement: &CreateRoleStmt,
) -> Result<(), SQLError> {
require_role_creation(&context.analysis)?;
let current = context.analysis.names.current_role();
{
let roles = context.analysis.roles.role_definitions();
require_role_attribute_authority(
&roles,
¤t,
statement.attributes.iter().copied(),
"create role",
)?;
}
let definition = identity::reserve_definition(context, statement)?;
require_role_creation(&context.analysis)?;
let roles = context.analysis.roles.role_definitions();
require_role_attribute_authority(
&roles,
¤t,
statement.attributes.iter().copied(),
"create role",
)?;
let (_, current_is_superuser) =
definition::create_role_candidate(&roles, ¤t, definition.clone())?;
drop(roles);
memberships::create(
context,
current,
statement,
definition,
current_is_superuser,
)
}
pub fn alter_role(
context: &RoleExecutionContext<'_>,
statement: &AlterRoleStmt,
) -> Result<(), SQLError> {
if statement.membership_action.is_some() {
return memberships::alter_group(context, statement);
}
let name = resolve_role_specification(context.analysis.names, &statement.name);
let current = context.analysis.names.current_role();
let (original, replacement) = {
let roles = context.analysis.roles.role_definitions();
let name = name.catalog_name(&roles)?;
let mut next = definition::alter_role_candidate(
&context.analysis,
&roles,
¤t,
name.clone(),
statement,
)?;
(
uqa_sql::catalog::roles::tuple::RoleTuple::bind(&roles[&name])?,
next.remove(&name).expect("validated role alteration"),
)
};
tuples::lock(context, &original)?;
context.publication.prepare_writer()?;
let mut roles = context.registry.write_roles();
original.revalidate(&roles)?;
let mut next = roles.clone();
next.insert(replacement.name.clone(), replacement);
context.publication.persist_roles(&roles, &next)?;
**roles = next;
drop(roles);
context.publication.catalog_changed();
Ok(())
}
pub fn grant_roles(
context: &RoleExecutionContext<'_>,
statement: &GrantRoleStmt,
) -> Result<(), SQLError> {
memberships::grant(
context,
statement,
statement
.granted_roles
.iter()
.cloned()
.map(RoleReference::Named)
.collect(),
)
}
#[cfg(test)]
mod tests;