use crate::ir::default_privileges::DefaultPrivilegeRule;
pub fn run(rules: &mut Vec<DefaultPrivilegeRule>) {
for rule in rules.iter_mut() {
super::grants::strip_public_implicit_grants(&mut rule.grants, rule.object_type);
super::grants::run_on_list(&mut rule.grants);
}
rules.retain(|r| !r.grants.is_empty());
rules.sort_by(|a, b| {
a.target_role
.as_str()
.cmp(b.target_role.as_str())
.then_with(|| match (&a.schema, &b.schema) {
(None, None) => std::cmp::Ordering::Equal,
(None, Some(_)) => std::cmp::Ordering::Less,
(Some(_), None) => std::cmp::Ordering::Greater,
(Some(x), Some(y)) => x.as_str().cmp(y.as_str()),
})
.then_with(|| a.object_type.pg_char().cmp(&b.object_type.pg_char()))
});
}
#[cfg(test)]
mod tests {
use super::*;
use crate::identifier::Identifier;
use crate::ir::default_privileges::{DefaultPrivObjectType, DefaultPrivilegeRule};
use crate::ir::grant::{Grant, GrantTarget, Privilege};
fn id(s: &str) -> Identifier {
Identifier::from_unquoted(s).unwrap()
}
fn rule(
target: &str,
schema: Option<&str>,
kind: DefaultPrivObjectType,
) -> DefaultPrivilegeRule {
DefaultPrivilegeRule {
target_role: id(target),
schema: schema.map(id),
object_type: kind,
grants: vec![Grant {
grantee: GrantTarget::Public,
privilege: Privilege::Select,
with_grant_option: false,
columns: None,
}],
}
}
#[test]
fn sorts_by_target_then_schema_then_type() {
let mut rules = vec![
rule("zebra", Some("z"), DefaultPrivObjectType::Tables),
rule("alice", Some("b"), DefaultPrivObjectType::Tables),
rule("alice", None, DefaultPrivObjectType::Sequences),
rule("alice", Some("a"), DefaultPrivObjectType::Tables),
];
run(&mut rules);
assert_eq!(rules[0].target_role.as_str(), "alice");
assert!(
rules[0].schema.is_none(),
"None schema sorts first for alice"
);
assert_eq!(rules[1].target_role.as_str(), "alice");
assert_eq!(rules[1].schema.as_ref().unwrap().as_str(), "a");
assert_eq!(rules[2].target_role.as_str(), "alice");
assert_eq!(rules[2].schema.as_ref().unwrap().as_str(), "b");
assert_eq!(rules[3].target_role.as_str(), "zebra");
}
#[test]
fn empty_grants_rule_is_stripped() {
let mut rules = vec![
DefaultPrivilegeRule {
target_role: id("alice"),
schema: None,
object_type: DefaultPrivObjectType::Tables,
grants: vec![Grant {
grantee: GrantTarget::Role(id("reader")),
privilege: Privilege::Select,
with_grant_option: false,
columns: None,
}],
},
DefaultPrivilegeRule {
target_role: id("app_owner"),
schema: None,
object_type: DefaultPrivObjectType::Schemas,
grants: vec![],
},
];
run(&mut rules);
assert_eq!(
rules.len(),
1,
"empty-grants rule should be stripped; remaining: {rules:?}"
);
assert_eq!(rules[0].target_role.as_str(), "alice");
}
#[test]
fn strips_implicit_public_grants_then_drops_empty_rule() {
let mut rules = vec![
DefaultPrivilegeRule {
target_role: id("app_owner"),
schema: Some(id("ops")),
object_type: DefaultPrivObjectType::Types,
grants: vec![Grant {
grantee: GrantTarget::Public,
privilege: Privilege::Usage,
with_grant_option: false,
columns: None,
}],
},
DefaultPrivilegeRule {
target_role: id("app_owner"),
schema: Some(id("ops")),
object_type: DefaultPrivObjectType::Functions,
grants: vec![Grant {
grantee: GrantTarget::Public,
privilege: Privilege::Execute,
with_grant_option: false,
columns: None,
}],
},
];
run(&mut rules);
assert!(
rules.is_empty(),
"implicit-only rules must be stripped to empty and removed; remaining: {rules:?}"
);
}
#[test]
fn strips_implicit_but_keeps_real_grants() {
let mut rules = vec![DefaultPrivilegeRule {
target_role: id("app_owner"),
schema: Some(id("ops")),
object_type: DefaultPrivObjectType::Types,
grants: vec![
Grant {
grantee: GrantTarget::Public,
privilege: Privilege::Usage,
with_grant_option: false,
columns: None,
},
Grant {
grantee: GrantTarget::Role(id("readers")),
privilege: Privilege::Usage,
with_grant_option: false,
columns: None,
},
],
}];
run(&mut rules);
assert_eq!(rules.len(), 1, "rule with a real grant survives");
assert_eq!(
rules[0].grants.len(),
1,
"only the implicit grant is stripped"
);
assert!(
matches!(&rules[0].grants[0].grantee, GrantTarget::Role(n) if n.as_str() == "readers"),
);
}
#[test]
fn keeps_wgo_public_grant_on_types() {
let mut rules = vec![DefaultPrivilegeRule {
target_role: id("app_owner"),
schema: Some(id("ops")),
object_type: DefaultPrivObjectType::Types,
grants: vec![Grant {
grantee: GrantTarget::Public,
privilege: Privilege::Usage,
with_grant_option: true,
columns: None,
}],
}];
run(&mut rules);
assert_eq!(rules.len(), 1, "WGO public grant is user-declared, kept");
assert!(rules[0].grants[0].with_grant_option);
}
#[test]
fn delegates_grant_list_to_grants_canon() {
let mut rules = vec![DefaultPrivilegeRule {
target_role: id("alice"),
schema: None,
object_type: DefaultPrivObjectType::Tables,
grants: vec![
Grant {
grantee: GrantTarget::Role(id("zelda")),
privilege: Privilege::Select,
with_grant_option: false,
columns: None,
},
Grant {
grantee: GrantTarget::Public,
privilege: Privilege::Select,
with_grant_option: false,
columns: None,
},
],
}];
run(&mut rules);
assert!(matches!(rules[0].grants[0].grantee, GrantTarget::Public));
}
}