use authz_resolver_sdk::pep::{AccessRequest, EnforcerError, PolicyEnforcer, ResourceType};
use toolkit_security::{AccessScope, SecurityContext, pep_properties};
use crate::domain::error::DomainError;
pub mod actions {
pub const ADMIN: &str = "admin";
pub const WRITE: &str = "write";
pub const READ: &str = "read";
pub const DELETE: &str = "delete";
}
#[must_use]
pub fn node_resource() -> ResourceType {
ResourceType::new(
graph_storage_sdk::gts::NODE_RESOURCE.to_owned(),
&[pep_properties::OWNER_TENANT_ID],
)
}
#[must_use]
pub fn type_resource() -> ResourceType {
ResourceType::new(
graph_storage_sdk::gts::TYPE_RESOURCE.to_owned(),
&[pep_properties::OWNER_TENANT_ID],
)
}
#[must_use]
pub fn map_enforcer_err(error: &EnforcerError) -> DomainError {
match error {
EnforcerError::Denied { .. } | EnforcerError::CompileFailed(_) => DomainError::AccessDenied,
EnforcerError::EvaluationFailed(_) => DomainError::Unavailable {
detail: "authorization evaluation failed".to_owned(),
},
}
}
pub async fn scope_for(
enforcer: &PolicyEnforcer,
ctx: &SecurityContext,
resource: &ResourceType,
action: &str,
) -> Result<AccessScope, DomainError> {
let tenant = ctx.subject_tenant_id();
let request = AccessRequest::new()
.resource_property(pep_properties::OWNER_TENANT_ID, tenant)
.require_constraints(true);
enforcer
.access_scope_with(ctx, resource, action, None, &request)
.await
.map_err(|error| map_enforcer_err(&error))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_denial_and_an_outage_are_not_the_same_answer() {
let denied = map_enforcer_err(&EnforcerError::Denied { deny_reason: None });
assert!(
matches!(denied, DomainError::AccessDenied),
"a denial is a denial: {denied:?}"
);
let uncompilable = map_enforcer_err(&EnforcerError::CompileFailed(
authz_resolver_sdk::pep::ConstraintCompileError::AllConstraintsFailed {
reason: "unrepresentable constraint".to_owned(),
},
));
assert!(
matches!(uncompilable, DomainError::AccessDenied),
"a scope that cannot be compiled fails closed: {uncompilable:?}"
);
let outage = map_enforcer_err(&EnforcerError::EvaluationFailed(
toolkit_canonical_errors::CanonicalError::internal("connection refused").create(),
));
assert!(
matches!(outage, DomainError::Unavailable { .. }),
"an unreachable PDP is an outage, not a decision: {outage:?}"
);
}
}