use crate::schema::foreign_table_alteration::{
bound_foreign_table_security, ForeignTableAlterCatalog,
};
use uqa_core::RelationIdentity;
use uqa_sql::{
catalog::{
roles::{guards::RoleCatalogGuards, role_inherits, RoleReferenceNames},
security::{
ownership::RelationOwnerSchemas,
table::{role_has_table_privilege, TableAclPrivilege},
TableSecurity,
},
},
SQLError,
};
use uqa_storage::CatalogFacade;
pub struct ForeignAuthorizationContext<'a> {
pub names: &'a dyn RoleReferenceNames,
pub roles: &'a dyn RoleCatalogGuards,
pub schemas: &'a dyn RelationOwnerSchemas,
pub catalog: &'a dyn ForeignTableAlterCatalog,
}
impl ForeignAuthorizationContext<'_> {
pub fn ensure_foreign_table_owner(&self, name: &str) -> Result<String, SQLError> {
let (relation, security) = bound_foreign_table_security(self.catalog, name)?;
if self.current_user_has_role_privileges(&security.role_owner) {
return Ok(security.role_owner);
}
Err(SQLError::Routine {
sqlstate: "42501".into(),
message: format!("must be owner of foreign table {}", relation.name),
})
}
pub fn ensure_foreign_table_privilege(
&self,
name: &str,
privilege: TableAclPrivilege,
) -> Result<(), SQLError> {
let (relation, security) = bound_foreign_table_security(self.catalog, name)?;
let roles = self.roles.role_definitions();
let memberships = self.roles.role_memberships();
if role_has_table_privilege(
&security,
&self.names.current_user_name(),
privilege,
&roles,
&memberships,
) {
return Ok(());
}
Err(SQLError::Routine {
sqlstate: "42501".into(),
message: format!("permission denied for foreign table {}", relation.name),
})
}
pub fn ensure_foreign_table_drop_authority(&self, name: &str) -> Result<(), SQLError> {
let (relation, security) = bound_foreign_table_security(self.catalog, name)?;
if self.current_user_has_role_privileges(&security.role_owner)
|| self
.schemas
.schema_security(&relation.schema)
.is_some_and(|schema| self.current_user_has_role_privileges(&schema.role_owner))
{
return Ok(());
}
Err(SQLError::Routine {
sqlstate: "42501".into(),
message: format!("must be owner of foreign table {}", relation.name),
})
}
fn current_user_has_role_privileges(&self, target: &str) -> bool {
let current = self.names.current_user_name();
let roles = self.roles.role_definitions();
let memberships = self.roles.role_memberships();
role_inherits(&roles, &memberships, ¤t, target)
}
}
pub fn persist_foreign_table_security(
catalog: Option<&dyn CatalogFacade>,
relation: &RelationIdentity,
security: &TableSecurity,
) -> Result<(), SQLError> {
let Some(catalog) = catalog else {
return Ok(());
};
let updated = catalog
.update_foreign_table_security(
relation,
&security.role_owner,
security.acl.as_deref(),
&security.column_acls,
)
.map_err(|error| {
SQLError::Internal(format!(
"persist foreign table security for `{}`: {error}",
relation.qualified_name()
))
})?;
if !updated {
return Err(SQLError::Internal(format!(
"foreign table `{}` disappeared from durable catalog before security update",
relation.qualified_name()
)));
}
Ok(())
}