use std::collections::BTreeMap;
use sqlx::PgPool;
use pgroles_core::manifest::{ObjectType, Privilege};
use pgroles_core::model::{DefaultPrivKey, DefaultPrivState};
#[derive(Debug, sqlx::FromRow)]
struct DefaultAclRow {
owner_name: String,
schema_name: Option<String>,
grantee: Option<String>,
privilege_type: String,
obj_type_char: String,
}
fn defacl_obj_type_to_object_type(character: &str) -> Option<ObjectType> {
match character {
"r" => Some(ObjectType::Table),
"S" => Some(ObjectType::Sequence),
"f" => Some(ObjectType::Function),
"T" => Some(ObjectType::Type),
"n" => Some(ObjectType::Schema),
_ => None,
}
}
fn acl_char_to_privilege(character: &str) -> Option<Privilege> {
match character {
"r" | "SELECT" => Some(Privilege::Select),
"a" | "INSERT" => Some(Privilege::Insert),
"w" | "UPDATE" => Some(Privilege::Update),
"d" | "DELETE" => Some(Privilege::Delete),
"D" | "TRUNCATE" => Some(Privilege::Truncate),
"x" | "REFERENCES" => Some(Privilege::References),
"t" | "TRIGGER" => Some(Privilege::Trigger),
"X" | "EXECUTE" => Some(Privilege::Execute),
"U" | "USAGE" => Some(Privilege::Usage),
"C" | "CREATE" => Some(Privilege::Create),
"c" | "CONNECT" => Some(Privilege::Connect),
"T" | "TEMPORARY" => Some(Privilege::Temporary),
_ => None,
}
}
pub async fn fetch_default_privileges(
pool: &PgPool,
managed_schemas: &[&str],
managed_roles: &[&str],
) -> Result<BTreeMap<DefaultPrivKey, DefaultPrivState>, sqlx::Error> {
let rows = sqlx::query_as::<_, DefaultAclRow>(
r#"
SELECT
owner_role.rolname AS owner_name,
n.nspname AS schema_name,
grantee_role.rolname AS grantee,
acl.privilege_type,
da.defaclobjtype::text AS obj_type_char
FROM pg_default_acl da
JOIN pg_roles owner_role ON owner_role.oid = da.defaclrole
JOIN pg_namespace n ON n.oid = da.defaclnamespace
CROSS JOIN LATERAL aclexplode(da.defaclacl) AS acl
LEFT JOIN pg_roles grantee_role ON grantee_role.oid = acl.grantee
WHERE n.nspname = ANY($1)
AND da.defaclnamespace <> 0
ORDER BY owner_role.rolname, n.nspname, da.defaclobjtype
"#,
)
.bind(managed_schemas)
.fetch_all(pool)
.await?;
let mut defaults: BTreeMap<DefaultPrivKey, DefaultPrivState> = BTreeMap::new();
for row in rows {
let grantee = match row.grantee {
Some(ref name) => name,
None => continue,
};
if !managed_roles.contains(&grantee.as_str()) {
continue;
}
let schema_name = match row.schema_name {
Some(ref name) => name,
None => continue, };
let privilege = match acl_char_to_privilege(&row.privilege_type) {
Some(privilege) => privilege,
None => continue,
};
let on_type = match defacl_obj_type_to_object_type(&row.obj_type_char) {
Some(object_type) => object_type,
None => continue,
};
let key = DefaultPrivKey {
owner: row.owner_name.clone(),
schema: schema_name.clone(),
on_type,
grantee: grantee.clone(),
};
let entry = defaults.entry(key).or_insert_with(|| DefaultPrivState {
privileges: std::collections::BTreeSet::new(),
});
entry.privileges.insert(privilege);
}
Ok(defaults)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defacl_obj_type_mapping() {
assert_eq!(defacl_obj_type_to_object_type("r"), Some(ObjectType::Table));
assert_eq!(
defacl_obj_type_to_object_type("S"),
Some(ObjectType::Sequence)
);
assert_eq!(
defacl_obj_type_to_object_type("f"),
Some(ObjectType::Function)
);
assert_eq!(defacl_obj_type_to_object_type("T"), Some(ObjectType::Type));
assert_eq!(
defacl_obj_type_to_object_type("n"),
Some(ObjectType::Schema)
);
assert_eq!(defacl_obj_type_to_object_type("x"), None);
}
#[test]
fn acl_char_mapping_consistent() {
assert_eq!(acl_char_to_privilege("r"), Some(Privilege::Select));
assert_eq!(acl_char_to_privilege("a"), Some(Privilege::Insert));
assert_eq!(acl_char_to_privilege("w"), Some(Privilege::Update));
assert_eq!(acl_char_to_privilege("d"), Some(Privilege::Delete));
assert_eq!(acl_char_to_privilege("D"), Some(Privilege::Truncate));
assert_eq!(acl_char_to_privilege("x"), Some(Privilege::References));
assert_eq!(acl_char_to_privilege("t"), Some(Privilege::Trigger));
assert_eq!(acl_char_to_privilege("X"), Some(Privilege::Execute));
assert_eq!(acl_char_to_privilege("U"), Some(Privilege::Usage));
assert_eq!(acl_char_to_privilege("C"), Some(Privilege::Create));
assert_eq!(acl_char_to_privilege("c"), Some(Privilege::Connect));
assert_eq!(acl_char_to_privilege("T"), Some(Privilege::Temporary));
assert_eq!(acl_char_to_privilege("?"), None);
}
}