use std::collections::BTreeSet;
use rupg_analyze::PermInfo;
use rupg_catalog::{ObjRef, PG_CLASS, PG_LANGUAGE, PG_NAMESPACE, PG_PROC, PG_TYPE, RelKind};
use rupg_common::{Error, Result, SqlState};
use rupg_pgcatalog::builtin::{self, Owned};
use rupg_types::{RegKind, Value, qualified_name_list};
use crate::reg;
use crate::{Call, Kernel, Session, bad_value, type_error};
const ACL_INSERT: u32 = 1 << 0;
const ACL_SELECT: u32 = 1 << 1;
const ACL_UPDATE: u32 = 1 << 2;
const ACL_DELETE: u32 = 1 << 3;
const ACL_TRUNCATE: u32 = 1 << 4;
const ACL_REFERENCES: u32 = 1 << 5;
const ACL_TRIGGER: u32 = 1 << 6;
const ACL_EXECUTE: u32 = 1 << 7;
const ACL_USAGE: u32 = 1 << 8;
pub(crate) const ACL_CREATE: u32 = 1 << 9;
const ACL_CREATE_TEMP: u32 = 1 << 10;
pub(crate) const ACL_CONNECT: u32 = 1 << 11;
const ACL_SET: u32 = 1 << 12;
const ACL_ALTER_SYSTEM: u32 = 1 << 13;
const ACL_MAINTAIN: u32 = 1 << 14;
const ALL_GRANT_OPTIONS: u32 = 0xFFFF << 16;
const RIGHTS: &str = "arwdDxtXUCTcsAm";
const PUBLIC: u32 = 0;
const BOOTSTRAP_SUPERUSER: u32 = 10;
const PG_DATABASE_OWNER: u32 = 6171;
const PG_READ_ALL_DATA: u32 = 6181;
const PG_WRITE_ALL_DATA: u32 = 6182;
const PG_MAINTAIN: u32 = 6337;
const FIRST_UNPINNED_OBJECT_ID: u32 = 12000;
const PG_TOAST: u32 = 99;
const ARRAY_SUBSCRIPT_HANDLER: u32 = 6179;
type PrivMap = &'static [(&'static str, u32)];
const fn go(bits: u32) -> u32 {
bits << 16
}
const TABLE_PRIVS: PrivMap = &[
("SELECT", ACL_SELECT),
("SELECT WITH GRANT OPTION", go(ACL_SELECT)),
("INSERT", ACL_INSERT),
("INSERT WITH GRANT OPTION", go(ACL_INSERT)),
("UPDATE", ACL_UPDATE),
("UPDATE WITH GRANT OPTION", go(ACL_UPDATE)),
("DELETE", ACL_DELETE),
("DELETE WITH GRANT OPTION", go(ACL_DELETE)),
("TRUNCATE", ACL_TRUNCATE),
("TRUNCATE WITH GRANT OPTION", go(ACL_TRUNCATE)),
("REFERENCES", ACL_REFERENCES),
("REFERENCES WITH GRANT OPTION", go(ACL_REFERENCES)),
("TRIGGER", ACL_TRIGGER),
("TRIGGER WITH GRANT OPTION", go(ACL_TRIGGER)),
("MAINTAIN", ACL_MAINTAIN),
("MAINTAIN WITH GRANT OPTION", go(ACL_MAINTAIN)),
];
const SEQUENCE_PRIVS: PrivMap = &[
("USAGE", ACL_USAGE),
("USAGE WITH GRANT OPTION", go(ACL_USAGE)),
("SELECT", ACL_SELECT),
("SELECT WITH GRANT OPTION", go(ACL_SELECT)),
("UPDATE", ACL_UPDATE),
("UPDATE WITH GRANT OPTION", go(ACL_UPDATE)),
];
const COLUMN_PRIVS: PrivMap = &[
("SELECT", ACL_SELECT),
("SELECT WITH GRANT OPTION", go(ACL_SELECT)),
("INSERT", ACL_INSERT),
("INSERT WITH GRANT OPTION", go(ACL_INSERT)),
("UPDATE", ACL_UPDATE),
("UPDATE WITH GRANT OPTION", go(ACL_UPDATE)),
("REFERENCES", ACL_REFERENCES),
("REFERENCES WITH GRANT OPTION", go(ACL_REFERENCES)),
];
const DATABASE_PRIVS: PrivMap = &[
("CREATE", ACL_CREATE),
("CREATE WITH GRANT OPTION", go(ACL_CREATE)),
("TEMPORARY", ACL_CREATE_TEMP),
("TEMPORARY WITH GRANT OPTION", go(ACL_CREATE_TEMP)),
("TEMP", ACL_CREATE_TEMP),
("TEMP WITH GRANT OPTION", go(ACL_CREATE_TEMP)),
("CONNECT", ACL_CONNECT),
("CONNECT WITH GRANT OPTION", go(ACL_CONNECT)),
];
const USAGE_PRIVS: PrivMap = &[("USAGE", ACL_USAGE), ("USAGE WITH GRANT OPTION", go(ACL_USAGE))];
const FUNCTION_PRIVS: PrivMap =
&[("EXECUTE", ACL_EXECUTE), ("EXECUTE WITH GRANT OPTION", go(ACL_EXECUTE))];
const SCHEMA_PRIVS: PrivMap = &[
("CREATE", ACL_CREATE),
("CREATE WITH GRANT OPTION", go(ACL_CREATE)),
("USAGE", ACL_USAGE),
("USAGE WITH GRANT OPTION", go(ACL_USAGE)),
];
const TABLESPACE_PRIVS: PrivMap =
&[("CREATE", ACL_CREATE), ("CREATE WITH GRANT OPTION", go(ACL_CREATE))];
const PARAMETER_PRIVS: PrivMap = &[
("SET", ACL_SET),
("SET WITH GRANT OPTION", go(ACL_SET)),
("ALTER SYSTEM", ACL_ALTER_SYSTEM),
("ALTER SYSTEM WITH GRANT OPTION", go(ACL_ALTER_SYSTEM)),
];
const LARGEOBJECT_PRIVS: PrivMap = &[
("SELECT", ACL_SELECT),
("SELECT WITH GRANT OPTION", go(ACL_SELECT)),
("UPDATE", ACL_UPDATE),
("UPDATE WITH GRANT OPTION", go(ACL_UPDATE)),
];
const ROLE_PRIVS: PrivMap = &[
("USAGE", ACL_USAGE),
("MEMBER", ACL_CREATE),
("SET", ACL_SET),
("USAGE WITH GRANT OPTION", go(ACL_CREATE)),
("USAGE WITH ADMIN OPTION", go(ACL_CREATE)),
("MEMBER WITH GRANT OPTION", go(ACL_CREATE)),
("MEMBER WITH ADMIN OPTION", go(ACL_CREATE)),
("SET WITH GRANT OPTION", go(ACL_CREATE)),
("SET WITH ADMIN OPTION", go(ACL_CREATE)),
];
fn is_space(c: char) -> bool {
matches!(c, ' ' | '\t' | '\n' | '\r' | '\x0b' | '\x0c')
}
fn privileges(text: &str, map: PrivMap) -> Result<u32> {
let mut bits = 0;
for chunk in text.split(',') {
let chunk = chunk.trim_matches(is_space);
let Some(found) = map.iter().find(|p| p.0.eq_ignore_ascii_case(chunk)) else {
return Err(Error::new(
SqlState::INVALID_PARAMETER_VALUE,
format!("unrecognized privilege type: \"{chunk}\""),
));
};
bits |= found.1;
}
Ok(bits)
}
fn role_oid(name: &str, session: &dyn Session) -> Option<u32> {
rupg_pgcatalog::roles::current().oid(name).or_else(|| {
(name == session.user() || name == session.session_user()).then_some(BOOTSTRAP_SUPERUSER)
})
}
fn get_role_oid(name: &str, session: &dyn Session) -> Result<u32> {
role_oid(name, session).ok_or_else(|| {
Error::new(SqlState::UNDEFINED_OBJECT, format!("role \"{name}\" does not exist"))
})
}
fn get_role_oid_or_public(name: &str, session: &dyn Session) -> Result<u32> {
if name == "public" { Ok(PUBLIC) } else { get_role_oid(name, session) }
}
fn current_user(session: &dyn Session) -> u32 {
role_oid(&session.user(), session).unwrap_or(BOOTSTRAP_SUPERUSER)
}
fn superuser(role: u32) -> bool {
rupg_pgcatalog::roles::current().superuser(role)
}
#[derive(Clone, Copy, PartialEq)]
enum Recurse {
Members,
Privs,
SetRole,
}
fn roles_of(role: u32, how: Recurse, admin_of: Option<u32>) -> (Vec<u32>, bool) {
let created = rupg_pgcatalog::roles::current();
let mut roles = vec![role];
let mut admin = false;
let mut at = 0;
while let Some(&member) = roles.get(at) {
at += 1;
for edge in created.edges().filter(|m| m.member == member) {
if Some(edge.role) == admin_of && edge.admin {
admin = true;
}
if (how == Recurse::Privs && !edge.inherit) || (how == Recurse::SetRole && !edge.set) {
continue;
}
if !roles.contains(&edge.role) {
roles.push(edge.role);
}
}
if member == BOOTSTRAP_SUPERUSER && !roles.contains(&PG_DATABASE_OWNER) {
roles.push(PG_DATABASE_OWNER);
}
}
(roles, admin)
}
fn reaches(member: u32, role: u32, how: Recurse) -> bool {
member == role || superuser(member) || roles_of(member, how, None).0.contains(&role)
}
fn has_privs_of_role(member: u32, role: u32) -> bool {
reaches(member, role, Recurse::Privs)
}
pub(crate) fn has_pgstat_permissions(session: &dyn Session, role: u32) -> bool {
reads_all_stats(session) || has_privs_of_role(current_user(session), role)
}
pub(crate) fn reads_all_stats(session: &dyn Session) -> bool {
let user = current_user(session);
role_oid("pg_read_all_stats", session).is_some_and(|stats| has_privs_of_role(user, stats))
}
pub(crate) fn current_user_has(
kind: Owned,
oid: u32,
mask: u32,
session: &dyn Session,
) -> Result<bool> {
Ok(object_check(kind, oid, current_user(session), mask, session)?.unwrap_or(false))
}
fn is_admin_of_role(member: u32, role: u32) -> bool {
if superuser(member) {
return true;
}
member != role && roles_of(member, Recurse::Members, Some(role)).1
}
#[derive(Clone, Copy)]
enum AclKind {
Table,
Sequence,
Database,
Function,
Language,
Schema,
Tablespace,
Type,
Other,
Column,
LargeObject,
Parameter,
}
fn acl_default(kind: AclKind, owner: u32) -> Vec<(u32, u32)> {
let (world, all) = match kind {
AclKind::Table => (0, "arwdDxtm"),
AclKind::Sequence => (0, "rwU"),
AclKind::Database => (ACL_CREATE_TEMP | ACL_CONNECT, "CTc"),
AclKind::Function => (ACL_EXECUTE, "X"),
AclKind::Language | AclKind::Type => (ACL_USAGE, "U"),
AclKind::Schema => (0, "UC"),
AclKind::Tablespace => (0, "C"),
AclKind::Other => (0, "U"),
AclKind::Column => (0, ""),
AclKind::LargeObject => (0, "rw"),
AclKind::Parameter => (0, "sA"),
};
let all = all.chars().filter_map(|c| RIGHTS.find(c)).fold(0, |bits, at| bits | 1 << at);
let mut items = Vec::new();
if world != 0 {
items.push((PUBLIC, world));
}
if all != 0 {
items.push((owner, all));
}
items
}
fn parse_acl(acl: &[&str]) -> Result<Vec<(u32, u32)>> {
let bad = || Error::internal("a static aclitem that the engine cannot read");
let mut items = Vec::with_capacity(acl.len());
for item in acl {
let (grantee, rest) = item.split_once('=').ok_or_else(bad)?;
let (letters, _grantor) = rest.split_once('/').ok_or_else(bad)?;
let grantee = if grantee.is_empty() {
PUBLIC
} else {
let name = match grantee.strip_prefix('"').and_then(|g| g.strip_suffix('"')) {
Some(quoted) => quoted.replace("\"\"", "\""),
None => grantee.to_string(),
};
let Some(oid) = rupg_pgcatalog::roles::current().oid(&name) else { continue };
oid
};
let mut bits = 0;
let mut last = 0;
for c in letters.chars() {
if c == '*' {
bits |= last << 16;
} else {
last = 1 << RIGHTS.find(c).ok_or_else(bad)?;
bits |= last;
}
}
items.push((grantee, bits));
}
Ok(items)
}
fn acl_of(acl: Option<&[&str]>, kind: AclKind, owner: u32) -> Result<Vec<(u32, u32)>> {
match acl {
Some(acl) => parse_acl(acl),
None => Ok(acl_default(kind, owner)),
}
}
fn aclmask(acl: &[(u32, u32)], role: u32, owner: u32, mask: u32) -> u32 {
if mask & ALL_GRANT_OPTIONS != 0 && has_privs_of_role(role, owner) {
return mask & ALL_GRANT_OPTIONS;
}
let mut result = 0;
for &(grantee, bits) in acl {
if grantee == PUBLIC || grantee == role {
result |= bits & mask;
if result != 0 {
return result;
}
}
}
for &(grantee, bits) in acl {
if grantee == PUBLIC || grantee == role || bits & mask & !result == 0 {
continue;
}
if has_privs_of_role(role, grantee) {
result |= bits & mask;
if result != 0 {
return result;
}
}
}
result
}
type Check = Result<Option<bool>>;
struct Class<'a> {
oid: u32,
name: &'a str,
namespace: u32,
kind: char,
natts: i16,
owner: u32,
acl: Option<Vec<&'a str>>,
}
fn granted(object: ObjRef, session: &dyn Session) -> Option<Vec<&str>> {
let acl = session.catalog()?.object_acl(object)?;
Some(acl.iter().map(String::as_str).collect())
}
fn column_acl<'a>(
relid: u32,
num: i16,
acl: Option<&'a [&'a str]>,
session: &'a dyn Session,
) -> Option<Vec<&'a str>> {
match granted(ObjRef::column(relid, num), session) {
Some(acl) => Some(acl).filter(|acl| !acl.is_empty()),
None => acl.map(<[&str]>::to_vec),
}
}
fn class_of(relid: u32, session: &dyn Session) -> Option<Class<'_>> {
if let Some(class) = builtin::class_by_oid(relid) {
return Some(Class {
oid: class.oid,
name: class.name,
namespace: class.namespace,
kind: char::from(class.kind),
natts: class.natts,
owner: class.owner,
acl: granted(ObjRef::new(PG_CLASS, relid), session)
.or_else(|| class.acl.map(<[&str]>::to_vec)),
});
}
let rel = session.catalog()?.relation(relid)?;
Some(Class {
oid: rel.oid,
name: &rel.name,
namespace: rel.namespace,
kind: rel.kind.code(),
natts: i16::try_from(rel.columns.len()).unwrap_or(i16::MAX),
owner: rel.owner,
acl: rel.acl.as_ref().map(|acl| acl.iter().map(String::as_str).collect()),
})
}
struct Attribute<'a> {
name: &'a str,
num: i16,
dropped: bool,
acl: Option<Vec<&'a str>>,
}
const SYSTEM_COLUMNS: [(&str, i16); 6] =
[("ctid", -1), ("xmin", -2), ("cmin", -3), ("xmax", -4), ("cmax", -5), ("tableoid", -6)];
fn attributes_of(relid: u32, session: &dyn Session) -> Vec<Attribute<'_>> {
if builtin::class_by_oid(relid).is_some() {
return builtin::attributes(relid)
.iter()
.map(|a| Attribute {
name: a.name,
num: a.num,
dropped: a.dropped,
acl: column_acl(relid, a.num, a.acl, session),
})
.collect();
}
let Some(rel) = session.catalog().and_then(|c| c.relation(relid)) else { return Vec::new() };
let system: &[(&str, i16)] = match rel.kind {
RelKind::Table
| RelKind::Sequence
| RelKind::Toast
| RelKind::Partitioned
| RelKind::MatView => &SYSTEM_COLUMNS,
RelKind::Index | RelKind::PartitionedIndex | RelKind::View | RelKind::Composite => &[],
};
let system = system.iter().map(|&(name, num)| Attribute {
name,
num,
dropped: false,
acl: column_acl(relid, num, None, session),
});
let user = rel.columns.iter().zip(1..).map(|(column, num)| Attribute {
name: &column.name,
num,
dropped: column.dropped,
acl: column_acl(relid, num, None, session),
});
system.chain(user).collect()
}
fn is_system_class(class: &Class<'_>) -> bool {
class.namespace == PG_TOAST || class.oid < FIRST_UNPINNED_OBJECT_ID
}
fn class_check(relid: u32, role: u32, mask: u32, session: &dyn Session) -> Check {
let Some(class) = class_of(relid, session) else { return Ok(None) };
let mut mask = mask;
let changes = ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE;
if mask & changes != 0 && is_system_class(&class) && class.kind != 'v' && !superuser(role) {
mask &= !changes;
}
if superuser(role) {
return Ok(Some(mask != 0));
}
let kind = if class.kind == 'S' { AclKind::Sequence } else { AclKind::Table };
let acl = acl_of(class.acl.as_deref(), kind, class.owner)?;
let mut result = aclmask(&acl, role, class.owner, mask);
if mask & ACL_SELECT != 0
&& result & ACL_SELECT == 0
&& has_privs_of_role(role, PG_READ_ALL_DATA)
{
result |= ACL_SELECT;
}
let writes = ACL_INSERT | ACL_UPDATE | ACL_DELETE;
if mask & writes != 0 && result & writes == 0 && has_privs_of_role(role, PG_WRITE_ALL_DATA) {
result |= mask & writes;
}
if mask & ACL_MAINTAIN != 0
&& result & ACL_MAINTAIN == 0
&& has_privs_of_role(role, PG_MAINTAIN)
{
result |= ACL_MAINTAIN;
}
Ok(Some(result != 0))
}
fn attribute_check(relid: u32, attnum: i16, role: u32, mask: u32, session: &dyn Session) -> Check {
let attributes = attributes_of(relid, session);
let found = attributes.iter().find(|a| a.num == attnum && !a.dropped);
let (Some(attribute), Some(class)) = (found, class_of(relid, session)) else {
return Ok(None);
};
let Some(acl) = &attribute.acl else { return Ok(Some(false)) };
Ok(Some(aclmask(&parse_acl(acl)?, role, class.owner, mask) != 0))
}
fn all_attributes_check(relid: u32, role: u32, mask: u32, session: &dyn Session) -> Check {
let Some(class) = class_of(relid, session) else { return Ok(None) };
for attribute in attributes_of(relid, session) {
if attribute.num < 1 || attribute.num > class.natts || attribute.dropped {
continue;
}
let Some(acl) = &attribute.acl else { continue };
if aclmask(&parse_acl(acl)?, role, class.owner, mask) != 0 {
return Ok(Some(true));
}
}
Ok(Some(false))
}
fn column_check(relid: u32, attnum: i16, role: u32, mask: u32, session: &dyn Session) -> Check {
if attnum == 0 {
return Ok(None);
}
match attribute_check(relid, attnum, role, mask, session)? {
Some(true) => return Ok(Some(true)),
None => return Ok(None),
Some(false) => {}
}
class_check(relid, role, mask, session)
}
fn database_acl(oid: u32, session: &dyn Session) -> Result<Option<Privileges>> {
if let Some(row) = builtin::owned_by_oid(Owned::Database, oid) {
return Ok(Some((acl_of(row.acl, AclKind::Database, row.owner)?, row.owner)));
}
let created = session.databases();
let Some(database) = created.iter().find(|d| d.oid == oid) else { return Ok(None) };
let acl = match &database.acl {
Some(items) => parse_acl(&items.iter().map(String::as_str).collect::<Vec<_>>())?,
None => acl_default(AclKind::Database, database.owner),
};
Ok(Some((acl, database.owner)))
}
fn owned_kind(kind: Owned) -> AclKind {
match kind {
Owned::Namespace => AclKind::Schema,
Owned::Database => AclKind::Database,
Owned::Function => AclKind::Function,
Owned::Type => AclKind::Type,
Owned::Language => AclKind::Language,
Owned::Tablespace => AclKind::Tablespace,
Owned::ForeignDataWrapper | Owned::ForeignServer => AclKind::Other,
Owned::LargeObject => AclKind::LargeObject,
}
}
type Privileges = (Vec<(u32, u32)>, u32);
fn owned_class(kind: Owned) -> Option<u32> {
match kind {
Owned::Namespace => Some(PG_NAMESPACE),
Owned::Type => Some(PG_TYPE),
Owned::Function => Some(PG_PROC),
Owned::Language => Some(PG_LANGUAGE),
_ => None,
}
}
fn granted_object(kind: Owned, oid: u32, session: &dyn Session) -> Option<Vec<&str>> {
granted(ObjRef::new(owned_class(kind)?, oid), session)
}
fn session_object(kind: Owned, oid: u32, session: &dyn Session) -> Result<Option<Privileges>> {
let Some(catalog) = session.catalog() else { return Ok(None) };
let (acl, owner) = match kind {
Owned::Namespace => match catalog.schema(oid) {
Some(schema) => (schema.acl.as_ref(), schema.owner),
None => return Ok(None),
},
Owned::Type => match catalog.type_by_oid(oid) {
Some(ty) => (None, ty.owner),
None => return Ok(None),
},
Owned::Function => match catalog.function(oid) {
Some(function) => (None, function.owner),
None => return Ok(None),
},
Owned::Language => match catalog.languages().find(|l| l.oid == oid) {
Some(language) => (None, language.owner),
None => return Ok(None),
},
Owned::Tablespace => {
let created = session.tablespaces();
let Some(tablespace) = created.iter().find(|t| t.oid == oid) else { return Ok(None) };
let acl: Option<Vec<&str>> =
tablespace.acl.as_ref().map(|acl| acl.iter().map(String::as_str).collect());
return Ok(Some((
acl_of(acl.as_deref(), AclKind::Tablespace, tablespace.owner)?,
tablespace.owner,
)));
}
_ => return Ok(None),
};
let acl: Option<Vec<&str>> = granted_object(kind, oid, session)
.or_else(|| acl.map(|acl| acl.iter().map(String::as_str).collect()));
Ok(Some((acl_of(acl.as_deref(), owned_kind(kind), owner)?, owner)))
}
fn object_check(kind: Owned, oid: u32, role: u32, mask: u32, session: &dyn Session) -> Check {
if superuser(role) {
return Ok(Some(mask != 0));
}
let mut oid = oid;
if kind == Owned::Type {
if let Some(ty) = builtin::type_by_oid(oid) {
if ty.elem != 0 && ty.subscript == ARRAY_SUBSCRIPT_HANDLER {
oid = ty.elem;
}
if builtin::type_by_oid(oid).is_some_and(|t| t.kind == b'm') {
let Some(range) = builtin::multirange_range(oid) else { return Ok(None) };
oid = range;
}
} else {
let found = session.catalog().and_then(|c| c.type_by_oid(oid));
let Some(ty) = found else { return Ok(None) };
if ty.element != 0 {
oid = ty.element;
}
}
}
let (acl, owner) = if kind == Owned::Database {
let Some(found) = database_acl(oid, session)? else { return Ok(None) };
found
} else if let Some(row) = builtin::owned_by_oid(kind, oid) {
let acl = granted_object(kind, oid, session).or_else(|| row.acl.map(<[&str]>::to_vec));
(acl_of(acl.as_deref(), owned_kind(kind), row.owner)?, row.owner)
} else {
let Some(object) = session_object(kind, oid, session)? else { return Ok(None) };
object
};
let mut result = aclmask(&acl, role, owner, mask);
if kind == Owned::Namespace
&& mask & ACL_USAGE != 0
&& result & ACL_USAGE == 0
&& (has_privs_of_role(role, PG_READ_ALL_DATA) || has_privs_of_role(role, PG_WRITE_ALL_DATA))
{
result |= ACL_USAGE;
}
Ok(Some(result != 0))
}
fn role_check(role: u32, member: u32, mask: u32) -> bool {
(mask & go(ACL_CREATE) != 0 && is_admin_of_role(member, role))
|| (mask & ACL_CREATE != 0 && reaches(member, role, Recurse::Members))
|| (mask & ACL_USAGE != 0 && reaches(member, role, Recurse::Privs))
|| (mask & ACL_SET != 0 && reaches(member, role, Recurse::SetRole))
}
fn parameter_check(role: u32, mask: u32) -> bool {
superuser(role) && mask != 0
}
fn text(args: &[Value], index: usize) -> Result<&str> {
args.get(index).and_then(Value::as_str).ok_or_else(bad_value)
}
fn role_arg(call: &Call<'_>, args: &[Value], with_role: bool, public: bool) -> Result<u32> {
if !with_role {
return Ok(current_user(call.session));
}
match &args[0] {
Value::Oid(oid) => Ok(*oid),
Value::Text(name) if public => get_role_oid_or_public(name, call.session),
Value::Text(name) => get_role_oid(name, call.session),
_ => Err(bad_value()),
}
}
fn table_name(text: &str, session: &dyn Session) -> Result<u32> {
let names = qualified_name_list(text).map_err(type_error)?;
reg::relation_oid(&names, session)
}
fn reg_name(kind: RegKind, text: &str, session: &dyn Session, what: &str) -> Result<u32> {
match reg::input(kind, text, session)?? {
0 => Err(Error::new(
if kind == RegKind::Procedure {
SqlState::UNDEFINED_FUNCTION
} else {
SqlState::UNDEFINED_OBJECT
},
format!("{what} \"{text}\" does not exist"),
)),
oid => Ok(oid),
}
}
pub(crate) fn simple_name(kind: Owned, name: &str, session: &dyn Session) -> Result<u32> {
let mut found = builtin::owned(kind).iter().find(|r| r.name == name).map(|r| r.oid);
if found.is_none() && kind == Owned::Namespace {
found = session.catalog().and_then(|c| c.schema_by_name(name)).map(|s| s.oid);
}
if found.is_none() && kind == Owned::Database {
found = session.databases().iter().find(|d| d.name == name).map(|d| d.oid);
}
if found.is_none() && kind == Owned::Tablespace {
found = session.tablespaces().iter().find(|t| t.name == name).map(|t| t.oid);
}
found.ok_or_else(|| {
let (state, what) = match kind {
Owned::Namespace => (SqlState::UNDEFINED_SCHEMA, "schema"),
Owned::Database => (SqlState::UNDEFINED_DATABASE, "database"),
Owned::Language => (SqlState::UNDEFINED_OBJECT, "language"),
Owned::Tablespace => (SqlState::UNDEFINED_OBJECT, "tablespace"),
Owned::ForeignDataWrapper => (SqlState::UNDEFINED_OBJECT, "foreign-data wrapper"),
_ => (SqlState::UNDEFINED_OBJECT, "server"),
};
Error::new(state, format!("{what} \"{name}\" does not exist"))
})
}
fn owned_privs(kind: Owned) -> PrivMap {
match kind {
Owned::Namespace => SCHEMA_PRIVS,
Owned::Database => DATABASE_PRIVS,
Owned::Function => FUNCTION_PRIVS,
Owned::Tablespace => TABLESPACE_PRIVS,
Owned::LargeObject => LARGEOBJECT_PRIVS,
Owned::Type | Owned::Language | Owned::ForeignDataWrapper | Owned::ForeignServer => {
USAGE_PRIVS
}
}
}
fn result(check: Option<bool>) -> Value {
check.map_or(Value::Null, Value::Bool)
}
fn has_table_privilege(call: &Call<'_>, args: &[Value]) -> Result<Value> {
let with_role = args.len() == 3;
let role = role_arg(call, args, with_role, true)?;
let at = usize::from(with_role);
let relid = match &args[at] {
Value::Text(name) => table_name(name, call.session)?,
Value::Oid(oid) => *oid,
_ => return Err(bad_value()),
};
let mask = privileges(text(args, at + 1)?, TABLE_PRIVS)?;
Ok(result(class_check(relid, role, mask, call.session)?))
}
pub fn lock_table_allowed(relid: u32, role: u32, mode: i32, session: &dyn Session) -> Result<bool> {
let mut mask = ACL_MAINTAIN | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
if mode <= 1 {
mask |= ACL_SELECT;
}
if mode <= 3 {
mask |= ACL_INSERT;
}
Ok(class_check(relid, role, mask, session)?.unwrap_or(false))
}
pub fn insert_allowed(
relid: u32,
role: u32,
attnums: &[i16],
session: &dyn Session,
) -> Result<bool> {
if class_check(relid, role, ACL_INSERT, session)?.unwrap_or(false) {
return Ok(true);
}
if attnums.is_empty() {
return Ok(all_attributes_check(relid, role, ACL_INSERT, session)?.unwrap_or(false));
}
for &attnum in attnums {
if !attribute_check(relid, attnum, role, ACL_INSERT, session)?.unwrap_or(false) {
return Ok(false);
}
}
Ok(true)
}
fn class_bits(relid: u32, role: u32, mask: u32, session: &dyn Session) -> Result<u32> {
let mut result = 0;
for bit in [ACL_INSERT, ACL_SELECT, ACL_UPDATE, ACL_DELETE] {
if mask & bit != 0 && class_check(relid, role, bit, session)?.unwrap_or(false) {
result |= bit;
}
}
Ok(result)
}
fn each_attribute_check(relid: u32, role: u32, mask: u32, session: &dyn Session) -> Result<bool> {
let Some(class) = class_of(relid, session) else { return Ok(false) };
let mut result = false;
for attribute in attributes_of(relid, session) {
if attribute.num < 1 || attribute.num > class.natts || attribute.dropped {
continue;
}
let Some(acl) = &attribute.acl else { return Ok(false) };
if aclmask(&parse_acl(acl)?, role, class.owner, mask) == 0 {
return Ok(false);
}
result = true;
}
Ok(result)
}
fn modified_allowed(
relid: u32,
role: u32,
columns: &BTreeSet<i16>,
mask: u32,
session: &dyn Session,
) -> Result<bool> {
if columns.is_empty() {
return Ok(all_attributes_check(relid, role, mask, session)?.unwrap_or(false));
}
for &attnum in columns {
if !attribute_check(relid, attnum, role, mask, session)?.unwrap_or(false) {
return Ok(false);
}
}
Ok(true)
}
fn one_relation_allowed(perm: &PermInfo, role: u32, session: &dyn Session) -> Result<bool> {
let relid = perm.relid;
let remaining = perm.required & !class_bits(relid, role, perm.required, session)?;
if remaining == 0 {
return Ok(true);
}
if remaining & !(ACL_SELECT | ACL_INSERT | ACL_UPDATE) != 0 {
return Ok(false);
}
if remaining & ACL_SELECT != 0 {
if perm.selected.is_empty()
&& !all_attributes_check(relid, role, ACL_SELECT, session)?.unwrap_or(false)
{
return Ok(false);
}
for &attnum in &perm.selected {
let allowed = if attnum == 0 {
each_attribute_check(relid, role, ACL_SELECT, session)?
} else {
attribute_check(relid, attnum, role, ACL_SELECT, session)?.unwrap_or(false)
};
if !allowed {
return Ok(false);
}
}
}
if remaining & ACL_INSERT != 0
&& !modified_allowed(relid, role, &perm.inserted, ACL_INSERT, session)?
{
return Ok(false);
}
if remaining & ACL_UPDATE != 0
&& !modified_allowed(relid, role, &perm.updated, ACL_UPDATE, session)?
{
return Ok(false);
}
Ok(true)
}
pub fn check_permissions(perms: &[PermInfo], session: &dyn Session) -> Result<()> {
for perm in perms {
if perm.required == 0 {
continue;
}
let role = if perm.check_as == 0 { current_user(session) } else { perm.check_as };
if superuser(role) {
continue;
}
let Some(class) = class_of(perm.relid, session) else { continue };
if one_relation_allowed(perm, role, session)? {
continue;
}
let object = match class.kind {
'v' => "view",
'm' => "materialized view",
'S' => "sequence",
'f' => "foreign table",
'i' | 'I' => "index",
_ => "table",
};
return Err(Error::new(
SqlState::INSUFFICIENT_PRIVILEGE,
format!("permission denied for {object} {}", class.name),
));
}
Ok(())
}
pub fn check_execute(funcs: &[u32], session: &dyn Session) -> Result<()> {
if funcs.is_empty() {
return Ok(());
}
let role = current_user(session);
if superuser(role) {
return Ok(());
}
for (i, &func) in funcs.iter().enumerate() {
if funcs[..i].contains(&func)
|| object_check(Owned::Function, func, role, ACL_EXECUTE, session)? != Some(false)
{
continue;
}
let proc = builtin::proc_by_oid(func);
let object = if proc.is_some_and(|p| p.kind == b'a') { "aggregate" } else { "function" };
let name = proc
.map(|p| p.name.to_string())
.or_else(|| session.catalog().and_then(|c| c.function(func)).map(|f| f.name.clone()));
return Err(Error::new(
SqlState::INSUFFICIENT_PRIVILEGE,
format!("permission denied for {object} {}", name.unwrap_or_default()),
));
}
Ok(())
}
pub(crate) const USAGE_UPDATE: u32 = ACL_USAGE | ACL_UPDATE;
pub(crate) const SELECT_USAGE: u32 = ACL_SELECT | ACL_USAGE;
pub(crate) const UPDATE: u32 = ACL_UPDATE;
pub(crate) fn sequence_readable(relid: u32, session: &dyn Session) -> Result<bool> {
sequence_allowed(relid, SELECT_USAGE, session)
}
pub(crate) fn sequence_allowed(relid: u32, mask: u32, session: &dyn Session) -> Result<bool> {
Ok(class_check(relid, current_user(session), mask, session)?.unwrap_or(false))
}
fn has_sequence_privilege(call: &Call<'_>, args: &[Value]) -> Result<Value> {
let with_role = args.len() == 3;
let role = role_arg(call, args, with_role, true)?;
let at = usize::from(with_role);
let mask = privileges(text(args, at + 1)?, SEQUENCE_PRIVS)?;
let (relid, shown) = match &args[at] {
Value::Text(name) => (table_name(name, call.session)?, name.clone()),
Value::Oid(oid) => match class_of(*oid, call.session) {
Some(class) => (*oid, class.name.to_string()),
None => return Ok(Value::Null),
},
_ => return Err(bad_value()),
};
if class_of(relid, call.session).is_none_or(|c| c.kind != 'S') {
return Err(Error::new(
SqlState::WRONG_OBJECT_TYPE,
format!("\"{shown}\" is not a sequence"),
));
}
Ok(result(class_check(relid, role, mask, call.session)?))
}
fn column_number(relid: u32, name: &str, session: &dyn Session) -> Result<i16> {
if let Some(attribute) = attributes_of(relid, session).iter().find(|a| a.name == name) {
return Ok(if attribute.dropped { 0 } else { attribute.num });
}
match class_of(relid, session) {
Some(class) => Err(Error::new(
SqlState::UNDEFINED_COLUMN,
format!("column \"{name}\" of relation \"{}\" does not exist", class.name),
)),
None => Ok(0),
}
}
pub(crate) fn attnum(relid: u32, name: &str, session: &dyn Session) -> Option<i16> {
attributes_of(relid, session).iter().find(|a| a.name == name && !a.dropped).map(|a| a.num)
}
fn has_column_privilege(call: &Call<'_>, args: &[Value]) -> Result<Value> {
let with_role = args.len() == 4;
let role = role_arg(call, args, with_role, true)?;
let at = usize::from(with_role);
let relid = match &args[at] {
Value::Text(name) => table_name(name, call.session)?,
Value::Oid(oid) => *oid,
_ => return Err(bad_value()),
};
let attnum = match &args[at + 1] {
Value::Text(name) => column_number(relid, name, call.session)?,
value => value.as_i64().and_then(|n| i16::try_from(n).ok()).ok_or_else(bad_value)?,
};
let mask = privileges(text(args, at + 2)?, COLUMN_PRIVS)?;
Ok(result(column_check(relid, attnum, role, mask, call.session)?))
}
fn has_any_column_privilege(call: &Call<'_>, args: &[Value]) -> Result<Value> {
let with_role = args.len() == 3;
let role = role_arg(call, args, with_role, true)?;
let at = usize::from(with_role);
let relid = match &args[at] {
Value::Text(name) => table_name(name, call.session)?,
Value::Oid(oid) => *oid,
_ => return Err(bad_value()),
};
let mask = privileges(text(args, at + 1)?, COLUMN_PRIVS)?;
Ok(result(match class_check(relid, role, mask, call.session)? {
Some(false) => all_attributes_check(relid, role, mask, call.session)?,
other => other,
}))
}
fn has_object_privilege(kind: Owned, call: &Call<'_>, args: &[Value]) -> Result<Value> {
let with_role = args.len() == 3;
let role = role_arg(call, args, with_role, true)?;
let at = usize::from(with_role);
let oid = match &args[at] {
Value::Text(name) => match kind {
Owned::Function => reg_name(RegKind::Procedure, name, call.session, "function")?,
Owned::Type => reg_name(RegKind::Type, name, call.session, "type")?,
_ => simple_name(kind, name, call.session)?,
},
Value::Oid(oid) => *oid,
_ => return Err(bad_value()),
};
let mask = privileges(text(args, at + 1)?, owned_privs(kind))?;
Ok(result(object_check(kind, oid, role, mask, call.session)?))
}
fn has_largeobject_privilege(call: &Call<'_>, args: &[Value]) -> Result<Value> {
let with_role = args.len() == 3;
let role = role_arg(call, args, with_role, true)?;
let at = usize::from(with_role);
let oid = args[at].as_oid().ok_or_else(bad_value)?;
let mask = privileges(text(args, at + 1)?, LARGEOBJECT_PRIVS)?;
if builtin::owned_by_oid(Owned::LargeObject, oid).is_none() {
return Ok(Value::Null);
}
Ok(result(object_check(Owned::LargeObject, oid, role, mask, call.session)?))
}
fn has_parameter_privilege(call: &Call<'_>, args: &[Value]) -> Result<Value> {
let with_role = args.len() == 3;
let at = usize::from(with_role);
let mask = privileges(text(args, at + 1)?, PARAMETER_PRIVS)?;
let role = role_arg(call, args, with_role, true)?;
text(args, at)?;
Ok(Value::Bool(parameter_check(role, mask)))
}
fn pg_has_role(call: &Call<'_>, args: &[Value]) -> Result<Value> {
let with_member = args.len() == 3;
let member = role_arg(call, args, with_member, false)?;
let at = usize::from(with_member);
let role = match &args[at] {
Value::Text(name) => get_role_oid(name, call.session)?,
Value::Oid(oid) => *oid,
_ => return Err(bad_value()),
};
let mask = privileges(text(args, at + 1)?, ROLE_PRIVS)?;
Ok(Value::Bool(role_check(role, member, mask)))
}
const RIGHT_NAMES: [&str; 15] = [
"INSERT",
"SELECT",
"UPDATE",
"DELETE",
"TRUNCATE",
"REFERENCES",
"TRIGGER",
"EXECUTE",
"USAGE",
"CREATE",
"TEMPORARY",
"CONNECT",
"SET",
"ALTER SYSTEM",
"MAINTAIN",
];
pub(crate) fn aclexplode(call: &Call<'_>, args: &[Value]) -> Result<Vec<Vec<Value>>> {
let Some(Value::Array(acl)) = args.first() else { return Err(bad_value()) };
let mut rows = Vec::new();
for item in acl.values.iter().flatten() {
let item = stored_item(item, call.session)?;
for (bit, name) in RIGHT_NAMES.iter().enumerate() {
if item.bits & (1 << bit) != 0 {
rows.push(vec![
Value::Oid(item.grantor),
Value::Oid(item.grantee),
Value::text(*name),
Value::Bool(item.bits & go(1 << bit) != 0),
]);
}
}
}
Ok(rows)
}
#[derive(Debug, Clone, Copy)]
struct AclItem {
grantee: u32,
bits: u32,
grantor: u32,
}
const NAMEDATALEN: usize = 64;
fn is_name_byte(c: u8) -> bool {
c >= 0x80 || c.is_ascii_alphanumeric() || c == b'_'
}
fn skip_spaces(s: &[u8]) -> &[u8] {
let n = s.iter().take_while(|&&c| is_space(char::from(c))).count();
&s[n..]
}
fn getid(s: &[u8]) -> Result<(String, &[u8])> {
let mut s = skip_spaces(s);
let mut name = Vec::new();
let mut in_quotes = false;
while let Some((&c, rest)) = s.split_first() {
if !(in_quotes || c == b'"' || is_name_byte(c)) {
break;
}
s = rest;
if c == b'"' {
if !in_quotes {
in_quotes = true;
continue;
}
if s.first() != Some(&b'"') {
in_quotes = false;
continue;
}
s = &s[1..];
}
if name.len() >= NAMEDATALEN - 1 {
return Err(Error::new(SqlState::NAME_TOO_LONG, "identifier too long")
.with_detail(format!("Identifier must be less than {NAMEDATALEN} characters.")));
}
name.push(c);
}
Ok((String::from_utf8_lossy(&name).into_owned(), skip_spaces(s)))
}
fn acl_syntax(message: impl Into<String>) -> Error {
Error::new(SqlState::INVALID_TEXT_REPRESENTATION, message)
}
fn acl_role(name: &str, session: &dyn Session, stored: bool) -> Result<u32> {
if stored
&& role_oid(name, session).is_none()
&& let Ok(oid) = name.parse::<u32>()
{
return Ok(oid);
}
get_role_oid(name, session)
}
fn aclparse<'a>(s: &'a [u8], session: &dyn Session, stored: bool) -> Result<(AclItem, &'a [u8])> {
let (mut name, mut s) = getid(s)?;
if s.first() != Some(&b'=') {
if name != "group" && name != "user" {
return Err(acl_syntax(format!("unrecognized key word: \"{name}\""))
.with_hint("ACL key word must be \"group\" or \"user\"."));
}
(name, s) = getid(s)?;
if name.is_empty() {
return Err(acl_syntax("missing name")
.with_hint("A name must follow the \"group\" or \"user\" key word."));
}
}
let Some(mut s) = s.strip_prefix(b"=") else {
return Err(acl_syntax("missing \"=\" sign"));
};
let mut bits = 0;
let mut read = 0;
while let Some((&c, rest)) = s.split_first() {
if c == b'*' {
bits |= go(read);
} else if c.is_ascii_alphabetic() {
let Some(bit) = RIGHTS.find(char::from(c)) else {
return Err(acl_syntax(format!(
"invalid mode character: must be one of \"{RIGHTS}\""
)));
};
read = 1 << bit;
bits |= read;
} else {
break;
}
s = rest;
}
let grantee = if name.is_empty() { PUBLIC } else { acl_role(&name, session, stored)? };
let grantor = if let Some(rest) = s.strip_prefix(b"/") {
let (name, rest) = getid(rest)?;
s = rest;
if name.is_empty() {
return Err(acl_syntax("a name must follow the \"/\" sign"));
}
acl_role(&name, session, stored)?
} else {
session.warn(Error::new(
SqlState::INVALID_GRANTOR,
format!("defaulting grantor to user ID {BOOTSTRAP_SUPERUSER}"),
));
BOOTSTRAP_SUPERUSER
};
Ok((AclItem { grantee, bits, grantor }, s))
}
pub(crate) fn aclitem_in(text: &str, session: &dyn Session) -> Result<String> {
let (item, rest) = aclparse(text.as_bytes(), session, false)?;
if !skip_spaces(rest).is_empty() {
return Err(acl_syntax("extra garbage at the end of the ACL specification"));
}
Ok(acl_item(item.grantee, item.bits, item.grantor))
}
fn stored_item(value: &Value, session: &dyn Session) -> Result<AclItem> {
let text = value.as_str().ok_or_else(bad_value)?;
Ok(aclparse(text.as_bytes(), session, true)?.0)
}
fn acl_items(value: &Value, session: &dyn Session) -> Result<Vec<AclItem>> {
let Value::Array(acl) = value else { return Err(bad_value()) };
if acl.dims.len() != 1 {
return Err(Error::new(
SqlState::INVALID_PARAMETER_VALUE,
"ACL arrays must be one-dimensional",
));
}
if acl.values.iter().any(Option::is_none) {
return Err(Error::new(
SqlState::NULL_VALUE_NOT_ALLOWED,
"ACL arrays must not contain null values",
));
}
acl.values.iter().flatten().map(|item| stored_item(item, session)).collect()
}
fn aclitem_eq(_call: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(Value::Bool(text(args, 0)? == text(args, 1)?))
}
fn aclcontains(call: &Call<'_>, args: &[Value]) -> Result<Value> {
let [acl, item] = args else { return Err(bad_value()) };
let items = acl_items(acl, call.session)?;
let item = stored_item(item, call.session)?;
Ok(Value::Bool(items.iter().any(|a| {
a.grantee == item.grantee && a.grantor == item.grantor && (a.bits & item.bits) == item.bits
})))
}
const ANY_PRIVS: PrivMap = &[
("SELECT", ACL_SELECT),
("INSERT", ACL_INSERT),
("UPDATE", ACL_UPDATE),
("DELETE", ACL_DELETE),
("TRUNCATE", ACL_TRUNCATE),
("REFERENCES", ACL_REFERENCES),
("TRIGGER", ACL_TRIGGER),
("EXECUTE", ACL_EXECUTE),
("USAGE", ACL_USAGE),
("CREATE", ACL_CREATE),
("TEMP", ACL_CREATE_TEMP),
("TEMPORARY", ACL_CREATE_TEMP),
("CONNECT", ACL_CONNECT),
("SET", ACL_SET),
("ALTER SYSTEM", ACL_ALTER_SYSTEM),
("MAINTAIN", ACL_MAINTAIN),
];
fn makeaclitem(_call: &Call<'_>, args: &[Value]) -> Result<Value> {
let (Some(Value::Oid(grantee)), Some(Value::Oid(grantor)), Some(Value::Bool(grantable))) =
(args.first(), args.get(1), args.get(3))
else {
return Err(bad_value());
};
let privs = privileges(text(args, 2)?, ANY_PRIVS)?;
let bits = if *grantable { privs | go(privs) } else { privs };
Ok(Value::text(acl_item(*grantee, bits, *grantor)))
}
fn aclitem_sum(item: AclItem) -> u32 {
(item.bits & 0xFFFF).wrapping_add(item.grantee).wrapping_add(item.grantor)
}
fn hash_aclitem(call: &Call<'_>, args: &[Value]) -> Result<Value> {
let [item] = args else { return Err(bad_value()) };
Ok(Value::Int4(aclitem_sum(stored_item(item, call.session)?).cast_signed()))
}
fn hash_aclitem_extended(call: &Call<'_>, args: &[Value]) -> Result<Value> {
let [item, Value::Int8(seed)] = args else { return Err(bad_value()) };
let sum = aclitem_sum(stored_item(item, call.session)?);
if *seed == 0 {
return Ok(Value::Int8(i64::from(sum)));
}
Ok(Value::Int8(crate::hash::uint32_extended(sum, seed.cast_unsigned()).cast_signed()))
}
fn aclinsert(_call: &Call<'_>, _args: &[Value]) -> Result<Value> {
Err(Error::new(SqlState::FEATURE_NOT_SUPPORTED, "aclinsert is no longer supported"))
}
fn aclremove(_call: &Call<'_>, _args: &[Value]) -> Result<Value> {
Err(Error::new(SqlState::FEATURE_NOT_SUPPORTED, "aclremove is no longer supported"))
}
fn put_role(out: &mut String, role: u32) {
let roles = rupg_pgcatalog::roles::current();
let Some(name) = roles.name(role) else {
out.push_str(&role.to_string());
return;
};
if name.bytes().all(|c| c.is_ascii_alphanumeric() || c == b'_') {
out.push_str(name);
return;
}
out.push('"');
for c in name.chars() {
if c == '"' {
out.push('"');
}
out.push(c);
}
out.push('"');
}
fn acl_item(grantee: u32, bits: u32, grantor: u32) -> String {
let mut out = String::new();
if grantee != PUBLIC {
put_role(&mut out, grantee);
}
out.push('=');
for (bit, letter) in RIGHTS.chars().enumerate() {
if bits & (1 << bit) != 0 {
out.push(letter);
if bits & go(1 << bit) != 0 {
out.push('*');
}
}
}
out.push('/');
put_role(&mut out, grantor);
out
}
fn acldefault(_call: &Call<'_>, args: &[Value]) -> Result<Value> {
let (Some(Value::Char(kind)), Some(Value::Oid(owner))) = (args.first(), args.get(1)) else {
return Err(bad_value());
};
let kind = match kind {
b'c' => AclKind::Column,
b'r' => AclKind::Table,
b's' => AclKind::Sequence,
b'd' => AclKind::Database,
b'f' => AclKind::Function,
b'l' => AclKind::Language,
b'L' => AclKind::LargeObject,
b'n' => AclKind::Schema,
b'p' => AclKind::Parameter,
b't' => AclKind::Tablespace,
b'F' | b'S' => AclKind::Other,
b'T' => AclKind::Type,
other => {
return Err(Error::internal(format!(
"unrecognized object type abbreviation: {}",
char::from(*other)
)));
}
};
let items = acl_default(kind, *owner)
.into_iter()
.map(|(grantee, bits)| Some(Value::text(acl_item(grantee, bits, *owner))))
.collect();
Ok(Value::Array(Box::new(rupg_types::Array::one(items))))
}
fn row_security_active(call: &Call<'_>, args: &[Value]) -> Result<Value> {
if let Some(Value::Text(name)) = args.first() {
table_name(name, call.session)?;
}
Ok(Value::Bool(false))
}
pub(crate) fn by_src(src: &str) -> Option<Kernel> {
if matches!(src, "row_security_active" | "row_security_active_name") {
return Some(row_security_active);
}
let kernel: Kernel = match src {
"acldefault_sql" => acldefault,
"aclitem_eq" => aclitem_eq,
"aclcontains" => aclcontains,
"makeaclitem" => makeaclitem,
"hash_aclitem" => hash_aclitem,
"hash_aclitem_extended" => hash_aclitem_extended,
"aclinsert" => aclinsert,
"aclremove" => aclremove,
_ => return privilege_kernel(src),
};
Some(kernel)
}
fn privilege_kernel(src: &str) -> Option<Kernel> {
let family = if src.starts_with("pg_has_role") {
"pg_has_role"
} else {
let end = src.find("_privilege")? + "_privilege".len();
&src[..end]
};
let kernel: Kernel = match family {
"has_table_privilege" => has_table_privilege,
"has_sequence_privilege" => has_sequence_privilege,
"has_column_privilege" => has_column_privilege,
"has_any_column_privilege" => has_any_column_privilege,
"has_database_privilege" => |c, a| has_object_privilege(Owned::Database, c, a),
"has_foreign_data_wrapper_privilege" => {
|c, a| has_object_privilege(Owned::ForeignDataWrapper, c, a)
}
"has_function_privilege" => |c, a| has_object_privilege(Owned::Function, c, a),
"has_language_privilege" => |c, a| has_object_privilege(Owned::Language, c, a),
"has_schema_privilege" => |c, a| has_object_privilege(Owned::Namespace, c, a),
"has_server_privilege" => |c, a| has_object_privilege(Owned::ForeignServer, c, a),
"has_tablespace_privilege" => |c, a| has_object_privilege(Owned::Tablespace, c, a),
"has_type_privilege" => |c, a| has_object_privilege(Owned::Type, c, a),
"has_largeobject_privilege" => has_largeobject_privilege,
"has_parameter_privilege" => has_parameter_privilege,
"pg_has_role" => pg_has_role,
_ => return None,
};
Some(kernel)
}