use rupg_catalog::{ObjRef, PG_CLASS, PG_NAMESPACE, PG_PROC, PG_TYPE, RelKind};
use rupg_common::{Error, Result, SqlState};
use rupg_pgcatalog::roles::Roles;
use rupg_sql::nodes::{
AlterDefaultPrivilegesStmt, DefElem, DropBehavior, Node, ObjectType, RoleSpecType,
};
use super::super::Definer;
use super::super::sequence::conflicting;
use super::{
ALL_FUNCTION, ALL_RELATION, ALL_SCHEMA, ALL_SEQUENCE, ALL_TYPE, AclItem, Grant, Kind, Object,
PUBLIC, SELECT, UPDATE, acl_texts, parse_acl, privilege_to_string, string_to_privilege,
};
use crate::typename::names;
const ALL_LARGEOBJECT: u64 = SELECT | UPDATE;
const RELATION: u8 = b'r';
const SEQUENCE: u8 = b'S';
const FUNCTION: u8 = b'f';
const TYPE: u8 = b'T';
const NAMESPACE: u8 = b'n';
const LARGEOBJECT: u8 = b'L';
fn kind_of(objtype: u8) -> Option<Kind> {
match objtype {
RELATION => Some(Kind::Table),
SEQUENCE => Some(Kind::Sequence),
FUNCTION => Some(Kind::Function),
TYPE => Some(Kind::Type),
NAMESPACE => Some(Kind::Schema),
_ => None,
}
}
fn hardwired(objtype: u8, owner: u32) -> Vec<AclItem> {
match kind_of(objtype) {
Some(kind) => kind.default_acl(owner),
None => vec![AclItem { grantee: owner, grantor: owner, privs: ALL_LARGEOBJECT }],
}
}
fn sorted(mut acl: Vec<AclItem>) -> Vec<AclItem> {
acl.sort_by_key(|item| (item.grantee, item.grantor, item.privs));
acl
}
fn aclmerge(
roles: &Roles,
left: Vec<AclItem>,
right: &[AclItem],
owner: u32,
) -> Result<Option<Vec<AclItem>>> {
if left.is_empty() && right.is_empty() {
return Ok(None);
}
let mut acl = left;
for &item in right {
acl = super::aclupdate(roles, &acl, item, true, owner, false)?;
}
Ok(Some(acl))
}
fn option_list(option: &DefElem) -> &[Option<Node>] {
match &option.arg {
Some(Node::List(list)) => list.as_slice(),
_ => &[],
}
}
impl Definer<'_, '_> {
pub(in crate::ddl) fn alter_default_privileges(
&mut self,
stmt: &AlterDefaultPrivilegesStmt,
) -> Result<()> {
let mut schemas: Option<&DefElem> = None;
let mut for_roles: Option<&DefElem> = None;
for option in stmt.options.iter().flatten() {
let Node::DefElem(option) = option else { return Err(Error::internal("an option")) };
let slot = match option.defname.as_deref() {
Some("schemas") => &mut schemas,
Some("roles") => &mut for_roles,
name => {
return Err(Error::internal(format!(
"option \"{}\" not recognized",
name.unwrap_or_default()
)));
}
};
if slot.is_some() {
return Err(conflicting(option));
}
*slot = Some(option);
}
let Some(action) = stmt.action.as_deref() else {
return Err(Error::internal("a GRANT statement"));
};
let mut grantees = Vec::new();
for role in action.grantees.iter().flatten() {
let Node::RoleSpec(role) = role else { return Err(Error::internal("a role")) };
grantees.push(if role.roletype == RoleSpecType::ROLESPEC_PUBLIC {
PUBLIC
} else {
self.role_oid(role)?
});
}
let (objtype, all, word) = match action.objtype {
ObjectType::OBJECT_TABLE => (RELATION, ALL_RELATION, "relation"),
ObjectType::OBJECT_SEQUENCE => (SEQUENCE, ALL_SEQUENCE, "sequence"),
ObjectType::OBJECT_FUNCTION => (FUNCTION, ALL_FUNCTION, "function"),
ObjectType::OBJECT_PROCEDURE => (FUNCTION, ALL_FUNCTION, "procedure"),
ObjectType::OBJECT_ROUTINE => (FUNCTION, ALL_FUNCTION, "routine"),
ObjectType::OBJECT_TYPE => (TYPE, ALL_TYPE, "type"),
ObjectType::OBJECT_SCHEMA => (NAMESPACE, ALL_SCHEMA, "schema"),
ObjectType::OBJECT_LARGEOBJECT => (LARGEOBJECT, ALL_LARGEOBJECT, "large object"),
other => {
return Err(Error::internal(format!(
"unrecognized GrantStmt.objtype: {}",
other.0
)));
}
};
let mut privileges = 0;
for privilege in action.privileges.iter().flatten() {
let Node::AccessPriv(privilege) = privilege else {
return Err(Error::internal("a privilege"));
};
if !privilege.cols.is_empty() {
return Err(Error::new(
SqlState::INVALID_GRANT_OPERATION,
"default privileges cannot be set for columns",
));
}
let Some(name) = privilege.priv_name.as_deref() else {
return Err(Error::internal("AccessPriv node must specify privilege"));
};
let bits = string_to_privilege(name)?;
if bits & !all != 0 {
return Err(Error::new(
SqlState::INVALID_GRANT_OPERATION,
format!("invalid privilege type {} for {word}", privilege_to_string(bits)),
));
}
privileges |= bits;
}
let grant = Grant {
is_grant: action.is_grant,
objtype: action.objtype,
all_privs: action.privileges.is_empty(),
privileges,
col_privs: Vec::new(),
grantees,
grant_option: action.grant_option,
cascade: action.behavior == DropBehavior::DROP_CASCADE,
grantor: None,
};
let schemas = schemas.map(option_list).unwrap_or_default();
let Some(for_roles) = for_roles else {
return self.set_default_acls_in_schemas(&grant, objtype, self.user, schemas);
};
for role in option_list(for_roles).iter().flatten() {
let Node::RoleSpec(role) = role else { return Err(Error::internal("a role")) };
let role = self.role_oid(role)?;
if !self.owns(role) {
return Err(Error::new(
SqlState::INSUFFICIENT_PRIVILEGE,
"permission denied to change default privileges",
));
}
self.set_default_acls_in_schemas(&grant, objtype, role, schemas)?;
}
Ok(())
}
fn set_default_acls_in_schemas(
&mut self,
grant: &Grant<'_>,
objtype: u8,
role: u32,
schemas: &[Option<Node>],
) -> Result<()> {
if schemas.is_empty() {
return self.set_default_acl(grant, objtype, role, 0);
}
for name in names(schemas) {
let namespace = self.an.schema(name).ok_or_else(|| {
Error::new(SqlState::UNDEFINED_SCHEMA, format!("schema \"{name}\" does not exist"))
})?;
self.set_default_acl(grant, objtype, role, namespace)?;
}
Ok(())
}
fn set_default_acl(
&mut self,
grant: &Grant<'_>,
objtype: u8,
role: u32,
namespace: u32,
) -> Result<()> {
if namespace != 0 && matches!(objtype, NAMESPACE | LARGEOBJECT) {
let what = if objtype == NAMESPACE {
"GRANT/REVOKE ON SCHEMAS"
} else {
"GRANT/REVOKE ON LARGE OBJECTS"
};
return Err(Error::new(
SqlState::INVALID_GRANT_OPERATION,
format!("cannot use IN SCHEMA clause when using {what}"),
));
}
let roles = rupg_pgcatalog::roles::current();
let default = if namespace == 0 { hardwired(objtype, role) } else { Vec::new() };
let old = match self.catalog.find_default_acl(role, namespace, objtype) {
Some(entry) => parse_acl(&roles, &entry.acl),
None => default.clone(),
};
let privileges = match (grant.all_privs, kind_of(objtype)) {
(false, _) => grant.privileges,
(true, Some(kind)) => kind.whole(),
(true, None) => ALL_LARGEOBJECT,
};
let new = sorted(Self::merge(&roles, grant, &old, privileges, role, role)?);
let acl = (new != sorted(default)).then(|| acl_texts(&roles, &new));
self.catalog.set_default_acl(role, namespace, objtype, acl);
Ok(())
}
pub(in crate::ddl) fn apply_default_acl(&mut self, object: ObjRef) -> Result<()> {
let oid = object.oid;
let found = match object.class {
PG_CLASS => self.catalog.relation(oid).and_then(|r| {
let objtype = match r.kind {
RelKind::Table | RelKind::View | RelKind::Partitioned | RelKind::MatView => {
RELATION
}
RelKind::Sequence => SEQUENCE,
_ => return None,
};
Some((objtype, r.owner, r.namespace))
}),
PG_TYPE => self.catalog.type_by_oid(oid).map(|t| (TYPE, t.owner, t.namespace)),
PG_PROC => self.catalog.function(oid).map(|f| (FUNCTION, f.owner, f.namespace)),
PG_NAMESPACE => self.catalog.schema(oid).map(|s| (NAMESPACE, s.owner, 0)),
_ => None,
};
let Some((objtype, owner, namespace)) = found else { return Ok(()) };
let roles = rupg_pgcatalog::roles::current();
let entry = |namespace: u32| {
self.catalog
.find_default_acl(owner, namespace, objtype)
.map(|d| parse_acl(&roles, &d.acl))
};
let global = entry(0);
let schema = if namespace == 0 { None } else { entry(namespace) };
if global.is_none() && schema.is_none() {
return Ok(());
}
let default = hardwired(objtype, owner);
let global = global.unwrap_or_else(|| default.clone());
let Some(acl) = aclmerge(&roles, global, schema.as_deref().unwrap_or_default(), owner)?
else {
return Ok(());
};
let acl = sorted(acl);
let Some(kind) = kind_of(objtype) else { return Ok(()) };
if acl == sorted(default) {
return Ok(());
}
self.write_acl(&roles, Object::new(kind, oid), &acl)
}
pub(super) fn remove_role_from_default_acl(&mut self, role: u32, oid: u32) -> Result<()> {
let Some(entry) = self.catalog.default_acl(oid) else { return Ok(()) };
let (owner, namespace, objtype) = (entry.role, entry.namespace, entry.objtype);
let grant = Grant {
is_grant: false,
objtype: ObjectType::OBJECT_TABLE,
all_privs: true,
privileges: 0,
col_privs: Vec::new(),
grantees: vec![role],
grant_option: false,
cascade: true,
grantor: None,
};
self.set_default_acl(&grant, objtype, owner, namespace)
}
}