#[cfg(feature = "cedar")]
use std::collections::HashMap;
#[cfg(feature = "cedar")]
use super::cedar::CedarAuthz;
#[cfg(feature = "cedar")]
use crate::auth::user::User;
#[cfg(feature = "cedar")]
#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone, Default)]
pub struct AuthzContext {
pub can_update: bool,
pub can_delete: bool,
pub can_create: bool,
pub can_read: bool,
pub permissions: HashMap<String, bool>,
}
#[cfg(feature = "cedar")]
pub struct AuthzContextBuilder<'a> {
cedar: &'a CedarAuthz,
user: &'a User,
update_path: Option<String>,
delete_path: Option<String>,
create_path: Option<String>,
read_path: Option<String>,
custom_checks: Vec<(String, String)>, }
#[cfg(feature = "cedar")]
impl<'a> AuthzContextBuilder<'a> {
const fn new(cedar: &'a CedarAuthz, user: &'a User) -> Self {
Self {
cedar,
user,
update_path: None,
delete_path: None,
create_path: None,
read_path: None,
custom_checks: Vec::new(),
}
}
#[must_use]
pub fn can_update(mut self, path: impl Into<String>) -> Self {
self.update_path = Some(path.into());
self
}
#[must_use]
pub fn can_delete(mut self, path: impl Into<String>) -> Self {
self.delete_path = Some(path.into());
self
}
#[must_use]
pub fn can_create(mut self, path: impl Into<String>) -> Self {
self.create_path = Some(path.into());
self
}
#[must_use]
pub fn can_read(mut self, path: impl Into<String>) -> Self {
self.read_path = Some(path.into());
self
}
#[must_use]
pub fn check(mut self, name: impl Into<String>, action: impl Into<String>) -> Self {
self.custom_checks.push((name.into(), action.into()));
self
}
pub async fn build(self) -> AuthzContext {
let mut context = AuthzContext::default();
if let Some(path) = self.update_path {
context.can_update = self.cedar.can_update(self.user, &path).await;
}
if let Some(path) = self.delete_path {
context.can_delete = self.cedar.can_delete(self.user, &path).await;
}
if let Some(path) = self.create_path {
context.can_create = self.cedar.can_create(self.user, &path).await;
}
if let Some(path) = self.read_path {
context.can_read = self.cedar.can_read(self.user, &path).await;
}
for (name, action) in self.custom_checks {
let allowed = self.cedar.can_perform(self.user, &action, None).await;
context.permissions.insert(name, allowed);
}
context
}
}
#[cfg(feature = "cedar")]
impl AuthzContext {
#[must_use]
pub const fn builder<'a>(cedar: &'a CedarAuthz, user: &'a User) -> AuthzContextBuilder<'a> {
AuthzContextBuilder::new(cedar, user)
}
#[must_use]
pub fn empty() -> Self {
Self {
can_update: false,
can_delete: false,
can_create: false,
can_read: false,
permissions: HashMap::new(),
}
}
#[must_use]
pub fn allow_all() -> Self {
Self {
can_update: true,
can_delete: true,
can_create: true,
can_read: true,
permissions: HashMap::new(),
}
}
#[must_use]
pub fn has_permission(&self, name: &str) -> Option<bool> {
self.permissions.get(name).copied()
}
}
#[cfg(test)]
#[cfg(feature = "cedar")]
mod tests {
use super::*;
#[test]
fn test_empty_context() {
let ctx = AuthzContext::empty();
assert!(!ctx.can_update);
assert!(!ctx.can_delete);
assert!(!ctx.can_create);
assert!(!ctx.can_read);
assert!(ctx.permissions.is_empty());
}
#[test]
fn test_allow_all_context() {
let ctx = AuthzContext::allow_all();
assert!(ctx.can_update);
assert!(ctx.can_delete);
assert!(ctx.can_create);
assert!(ctx.can_read);
}
#[test]
fn test_has_permission() {
let mut ctx = AuthzContext::empty();
assert!(ctx.has_permission("publish").is_none());
ctx.permissions.insert("publish".to_string(), true);
assert_eq!(ctx.has_permission("publish"), Some(true));
ctx.permissions.insert("archive".to_string(), false);
assert_eq!(ctx.has_permission("archive"), Some(false));
}
}