use super::{
lifecycle::{current_definition, lock_and_resolve},
TypeLifecycleContext,
};
use crate::catalog::security::roles::{
dependencies::{prepare_role_dependencies, RoleDependencyCandidate},
locking::RoleLockContext,
};
use crate::catalog::{domain, enum_type};
use std::collections::BTreeSet;
use uqa_core::catalog_role::RoleIdentity;
use uqa_sql::{
ast::{GrantTypeStmt, ObjectAclEntry, TypeRevokeBehavior},
catalog::roles::{
identity::RoleSubject, memberships::role_is_superuser, role_inherits, RoleDefinition,
},
catalog::security::{
acl_command::{AclCommandRoles, ResolvedAclRoles},
object_acl,
type_privileges::requested_type_usage,
},
schema::type_objects::{ResolvedTypeObject, TypeObject},
SQLError,
};
pub fn grant_type(
context: &TypeLifecycleContext<'_>,
statement: &GrantTypeStmt,
) -> Result<(), SQLError> {
context.writer.prepare_writer()?;
if let Some(grantor) = statement.grantor.as_ref() {
let roles = context.creation.roles.role_definitions();
let requested =
uqa_sql::catalog::roles::resolve_role_specification(context.creation.names, grantor)
.catalog_name(&roles)?;
validate_requested_grantor(&requested, &context.creation.names.current_role(), &roles)?;
}
let mut targets = Vec::with_capacity(statement.names.len());
for name in &statement.names {
targets.push(lock_and_resolve(context, name)?);
}
let mut command_roles = AclCommandRoles::default();
let RoleDependencyCandidate {
roles,
memberships,
value: TypePrivilegeCandidate { changes, notices },
..
} = prepare_role_dependencies(
&RoleLockContext {
roles: context.creation.roles,
session: context.identities.locks,
},
|| context.writer.prepare_writer(),
|| prepare(context, statement, &targets, &mut command_roles),
)?;
for object in changes {
publish(context, object)?;
}
drop(memberships);
drop(roles);
for notice in notices {
context.notices.notice(notice);
}
context.changes.catalog_registry_changed();
Ok(())
}
struct TypePrivilegeCandidate {
changes: Vec<TypeObject>,
notices: Vec<uqa_sql::SQLNotice>,
}
fn prepare<'a>(
context: &'a TypeLifecycleContext<'_>,
statement: &GrantTypeStmt,
targets: &[ResolvedTypeObject],
command_roles: &mut AclCommandRoles,
) -> Result<RoleDependencyCandidate<'a, TypePrivilegeCandidate>, SQLError> {
let roles = context.creation.roles.role_definitions();
let ResolvedAclRoles {
grantees,
current_user,
..
} = command_roles.resolve_validated(
context.creation.names,
&roles,
&statement.grantees,
statement.grantor.as_ref(),
|resolved| {
if let Some(grantor) = resolved.requested_grantor.as_deref() {
validate_requested_grantor(grantor, &resolved.current_user, &roles)?;
}
validate_grantees(resolved, &roles)
},
)?;
let usage = requested_type_usage(&statement.privileges, statement.kind)?;
let grantees = object_acl::bind_grantees(&grantees, &roles)?;
let memberships = context.creation.roles.role_memberships();
let superuser = role_is_superuser(&roles, ¤t_user);
let mut changes: Vec<TypeObject> = Vec::new();
let mut notices = Vec::new();
let mut dependencies = BTreeSet::new();
for target in targets {
target.require_grant_target(&context.binding, statement.kind)?;
if statement.is_grant && statement.grant_option && grantees.iter().any(Option::is_none) {
return Err(SQLError::Routine {
sqlstate: "0LP01".into(),
message: "grant options can only be granted to roles".into(),
});
}
let object = match changes.iter().find(|object| object.oid() == target.oid()) {
Some(object) => object.clone(),
None => {
target.clone().into_type_object(&context.binding)?;
current_definition(context, target.oid())?
}
};
let owner = object.owner();
let before = object.usage_acl().map(<[ObjectAclEntry]>::to_vec);
let local_name = object.identity().name.clone();
let grantor = object_acl::select_grantor(
owner,
before.as_deref(),
¤t_user,
&roles,
&memberships,
);
let mut acl = before.clone();
if let Some(grantor) = grantor.filter(|_| usage) {
apply_privileges(statement, owner, &mut acl, &grantees, grantor)?;
} else {
let has_role =
|role: &RoleIdentity| role_inherits(&roles, &memberships, ¤t_user, role);
if !object_acl::privilege_allowed(&owner, before.as_deref(), false, superuser, has_role)
{
return Err(SQLError::Routine {
sqlstate: "42501".into(),
message: format!("permission denied for type {local_name}"),
});
}
notices.push(object_acl::acl_warning(statement.is_grant, &local_name));
}
let acl = object_acl::explicit_acl(owner, acl);
object_acl::added_acl_roles(
(owner, before.as_deref()),
(owner, acl.as_deref()),
&roles,
"type",
&mut dependencies,
)?;
if acl != before {
let changed = with_acl(object, acl);
changes.retain(|object| object.oid() != changed.oid());
changes.push(changed);
}
}
Ok(RoleDependencyCandidate {
value: TypePrivilegeCandidate { changes, notices },
memberships,
roles,
dependencies,
})
}
fn validate_requested_grantor(
grantor: &str,
current_user: &uqa_sql::catalog::roles::RoleReference,
roles: &std::collections::BTreeMap<String, RoleDefinition>,
) -> Result<(), SQLError> {
if !roles.contains_key(grantor) {
return Err(SQLError::Routine {
sqlstate: "42704".into(),
message: format!("role \"{grantor}\" does not exist"),
});
}
if current_user.role_name(roles) != Some(grantor) {
return Err(SQLError::Routine {
sqlstate: "0A000".into(),
message: "grantor must be current user".into(),
});
}
Ok(())
}
fn validate_grantees(
resolved: &ResolvedAclRoles,
roles: &std::collections::BTreeMap<String, RoleDefinition>,
) -> Result<(), SQLError> {
for grantee in &resolved.grantees {
if let Some(name) = grantee
.role_name()
.filter(|name| !roles.contains_key(*name))
{
return Err(SQLError::Routine {
sqlstate: "42704".into(),
message: format!("role \"{name}\" does not exist"),
});
}
}
Ok(())
}
fn apply_privileges(
statement: &GrantTypeStmt,
owner: RoleIdentity,
acl: &mut Option<Vec<ObjectAclEntry>>,
grantees: &[Option<RoleIdentity>],
grantor: RoleIdentity,
) -> Result<(), SQLError> {
for grantee in grantees {
if statement.is_grant {
object_acl::grant(owner, acl, *grantee, grantor, statement.grant_option);
} else {
object_acl::revoke(
owner,
acl,
*grantee,
grantor,
statement.grant_option_only,
statement.revoke_behavior == TypeRevokeBehavior::Cascade,
)?;
}
}
Ok(())
}
fn with_acl(object: TypeObject, acl: Option<Vec<ObjectAclEntry>>) -> TypeObject {
match object {
TypeObject::Enum(mut definition) => {
definition.usage_acl = acl;
TypeObject::Enum(definition)
}
TypeObject::Domain(mut definition) => {
definition.usage_acl = acl;
TypeObject::Domain(definition)
}
TypeObject::Composite(mut definition) => {
definition.usage_acl = acl;
TypeObject::Composite(definition)
}
}
}
fn publish(context: &TypeLifecycleContext<'_>, object: TypeObject) -> Result<(), SQLError> {
match object {
TypeObject::Enum(definition) => {
let before = context.registries.enums.enum_registry().clone();
let mut registry = before.clone();
registry.insert(definition.identity.qualified_name(), definition);
enum_type::publish(context.registries.enums, &before, registry)
}
TypeObject::Domain(definition) => {
let before = context.registries.domains.domain_registry().clone();
let mut registry = before.clone();
registry.insert(definition.identity.qualified_name(), *definition);
domain::publish(context.registries.domains, &before, registry)
}
TypeObject::Composite(definition) => {
let before = context.registries.composites.composite_registry().clone();
let mut registry = before.clone();
registry.insert(definition.identity.qualified_name(), *definition);
crate::catalog::composite_type::publish(
context.registries.composites,
&before,
registry,
)
}
}
}