use crate::catalog::grant::{Grant, GranteeType, ObjectType};
use crate::diff::operations::{GrantOperation, MigrationStep};
use std::collections::BTreeMap;
pub fn is_owner_grant(grant: &Grant) -> bool {
match &grant.grantee {
GranteeType::Role(role_name) => role_name == &grant.object_owner,
GranteeType::Public => false, }
}
pub fn diff(old_grant: Option<&Grant>, new_grant: Option<&Grant>) -> Vec<MigrationStep> {
match (old_grant, new_grant) {
(None, Some(new)) => {
if is_owner_grant(new) {
vec![] } else {
vec![MigrationStep::Grant(GrantOperation::Grant {
grant: new.clone(),
})]
}
}
(Some(old), None) => {
if is_owner_grant(old) {
vec![] } else {
vec![MigrationStep::Grant(GrantOperation::Revoke {
grant: old.clone(),
})]
}
}
(Some(old), Some(new)) => {
if is_owner_grant(old) || is_owner_grant(new) {
vec![] } else {
let mut steps = Vec::new();
if old.privileges != new.privileges
|| old.with_grant_option != new.with_grant_option
{
steps.push(MigrationStep::Grant(GrantOperation::Revoke {
grant: old.clone(),
}));
steps.push(MigrationStep::Grant(GrantOperation::Grant {
grant: new.clone(),
}));
}
steps
}
}
(None, None) => vec![], }
}
pub fn diff_grants(old_grants: &[Grant], new_grants: &[Grant]) -> Vec<MigrationStep> {
let mut old_map = BTreeMap::new();
let mut new_map = BTreeMap::new();
for grant in old_grants {
old_map.insert(grant.id(), grant);
}
for grant in new_grants {
new_map.insert(grant.id(), grant);
}
let all_ids: std::collections::BTreeSet<_> =
old_map.keys().chain(new_map.keys()).cloned().collect();
let mut steps: Vec<MigrationStep> = all_ids
.into_iter()
.flat_map(|id| {
let old = old_map.get(&id).cloned();
let new = new_map.get(&id).cloned();
diff(old, new)
})
.collect();
steps.extend(generate_revoke_for_new_explicit_acls(
old_grants, new_grants,
));
steps
}
fn generate_revoke_for_new_explicit_acls(
old_grants: &[Grant],
new_grants: &[Grant],
) -> Vec<MigrationStep> {
let mut steps = Vec::new();
let mut old_by_object: BTreeMap<String, Vec<&Grant>> = BTreeMap::new();
for grant in old_grants {
old_by_object
.entry(object_key(&grant.object))
.or_default()
.push(grant);
}
let mut new_by_object: BTreeMap<String, Vec<&Grant>> = BTreeMap::new();
for grant in new_grants {
new_by_object
.entry(object_key(&grant.object))
.or_default()
.push(grant);
}
for (obj_key, new_object_grants) in &new_by_object {
let new_has_explicit_acl = new_object_grants.iter().any(|g| !g.is_default_acl);
if !new_has_explicit_acl {
continue; }
let old_had_explicit_acl = old_by_object
.get(obj_key)
.is_some_and(|old_grants| old_grants.iter().any(|g| !g.is_default_acl));
if old_had_explicit_acl {
continue;
}
let sample_grant = new_object_grants[0];
let expected_public_privileges = get_default_public_privileges(&sample_grant.object);
for privilege in expected_public_privileges {
let public_grant_exists = new_object_grants.iter().any(|g| {
matches!(&g.grantee, GranteeType::Public) && g.privileges.contains(&privilege)
});
if !public_grant_exists {
let revoke_grant = Grant {
grantee: GranteeType::Public,
object: sample_grant.object.clone(),
privileges: vec![privilege],
with_grant_option: false,
depends_on: vec![sample_grant.object.db_object_id()],
object_owner: sample_grant.object_owner.clone(),
is_default_acl: false,
};
steps.push(MigrationStep::Grant(GrantOperation::Revoke {
grant: revoke_grant,
}));
}
}
}
steps
}
fn object_key(object: &ObjectType) -> String {
match object {
ObjectType::Table { schema, name } => format!("table:{}.{}", schema, name),
ObjectType::View { schema, name } => format!("view:{}.{}", schema, name),
ObjectType::Schema { name } => format!("schema:{}", name),
ObjectType::Function {
schema,
name,
arguments,
} => format!("function:{}.{}({})", schema, name, arguments),
ObjectType::Procedure {
schema,
name,
arguments,
} => format!("procedure:{}.{}({})", schema, name, arguments),
ObjectType::Aggregate {
schema,
name,
arguments,
} => format!("aggregate:{}.{}({})", schema, name, arguments),
ObjectType::Sequence { schema, name } => format!("sequence:{}.{}", schema, name),
ObjectType::Type { schema, name } => format!("type:{}.{}", schema, name),
ObjectType::Domain { schema, name } => format!("domain:{}.{}", schema, name),
}
}
fn get_default_public_privileges(object: &ObjectType) -> Vec<String> {
match object {
ObjectType::Function { .. }
| ObjectType::Procedure { .. }
| ObjectType::Aggregate { .. } => {
vec!["EXECUTE".to_string()]
}
ObjectType::Type { .. } | ObjectType::Domain { .. } => {
vec!["USAGE".to_string()]
}
ObjectType::Table { .. }
| ObjectType::View { .. }
| ObjectType::Sequence { .. }
| ObjectType::Schema { .. } => {
vec![]
}
}
}