use super::error::AuthorizationError;
use super::gate::Gate;
use super::response::AuthResponse;
use crate::auth::Authenticatable;
use std::any::Any;
pub trait Authorizable: Authenticatable {
fn can(&self, ability: &str, resource: Option<&dyn Any>) -> bool
where
Self: Sized,
{
Gate::inspect(self, ability, resource).allowed()
}
fn cannot(&self, ability: &str, resource: Option<&dyn Any>) -> bool
where
Self: Sized,
{
!self.can(ability, resource)
}
fn authorize(&self, ability: &str, resource: Option<&dyn Any>) -> Result<(), AuthorizationError>
where
Self: Sized,
{
let response = Gate::inspect(self, ability, resource);
if response.allowed() {
Ok(())
} else {
let mut error = AuthorizationError::new(ability);
if let Some(msg) = response.message() {
error.message = Some(msg.to_string());
}
error.status = response.status();
Err(error)
}
}
fn check_ability(&self, ability: &str, resource: Option<&dyn Any>) -> AuthResponse
where
Self: Sized,
{
Gate::inspect(self, ability, resource)
}
}
impl<T: Authenticatable> Authorizable for T {}
#[cfg(test)]
mod tests {
use super::*;
use crate::authorization::gate::Gate;
use std::any::Any;
#[derive(Debug, Clone)]
struct TestUser {
id: i64,
role: String,
}
impl Authenticatable for TestUser {
fn auth_identifier(&self) -> i64 {
self.id
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[derive(Debug)]
struct TestResource {
owner_id: i64,
}
#[test]
fn test_can_method() {
let _guard = Gate::test_lock();
Gate::flush();
Gate::define("edit-resource", |user, resource| {
let user = user.as_any().downcast_ref::<TestUser>();
let resource = resource.and_then(|r| r.downcast_ref::<TestResource>());
match (user, resource) {
(Some(u), Some(r)) => (u.id == r.owner_id).into(),
_ => AuthResponse::deny_silent(),
}
});
let owner = TestUser {
id: 1,
role: "user".to_string(),
};
let other = TestUser {
id: 2,
role: "user".to_string(),
};
let resource = TestResource { owner_id: 1 };
assert!(owner.can("edit-resource", Some(&resource)));
assert!(!other.can("edit-resource", Some(&resource)));
}
#[test]
fn test_cannot_method() {
let _guard = Gate::test_lock();
Gate::flush();
Gate::define("admin-action", |user, _| {
user.as_any()
.downcast_ref::<TestUser>()
.map(|u| (u.role == "admin").into())
.unwrap_or_else(AuthResponse::deny_silent)
});
let admin = TestUser {
id: 1,
role: "admin".to_string(),
};
let regular = TestUser {
id: 2,
role: "user".to_string(),
};
assert!(!admin.cannot("admin-action", None));
assert!(regular.cannot("admin-action", None));
}
#[test]
fn test_authorize_method() {
let _guard = Gate::test_lock();
Gate::flush();
Gate::define("manage", |user, _| {
user.as_any()
.downcast_ref::<TestUser>()
.map(|u| {
if u.role == "manager" {
AuthResponse::allow()
} else {
AuthResponse::deny("Manager access required")
}
})
.unwrap_or_else(AuthResponse::deny_silent)
});
let manager = TestUser {
id: 1,
role: "manager".to_string(),
};
let regular = TestUser {
id: 2,
role: "user".to_string(),
};
assert!(manager.authorize("manage", None).is_ok());
let err = regular.authorize("manage", None).unwrap_err();
assert_eq!(err.message, Some("Manager access required".to_string()));
}
#[test]
fn test_check_ability() {
let _guard = Gate::test_lock();
Gate::flush();
Gate::define("view", |_, _| AuthResponse::allow());
let user = TestUser {
id: 1,
role: "user".to_string(),
};
let response = user.check_ability("view", None);
assert!(response.allowed());
}
}