use crate::{Authority, Role, SecurityContext};
#[derive(Debug, Clone)]
pub struct SecurityMetadata {
pub roles: Vec<Role>,
pub authorities: Vec<Authority>,
pub require_all: bool,
}
impl SecurityMetadata {
pub fn new() -> Self {
Self {
roles: Vec::new(),
authorities: Vec::new(),
require_all: true,
}
}
pub fn add_role(mut self, role: Role) -> Self {
self.roles.push(role);
self
}
pub fn add_roles(mut self, roles: Vec<Role>) -> Self {
self.roles.extend(roles);
self
}
pub fn add_authority(mut self, authority: Authority) -> Self {
self.authorities.push(authority);
self
}
pub fn require_all(mut self, require_all: bool) -> Self {
self.require_all = require_all;
self
}
pub async fn check(&self, context: &SecurityContext) -> Result<(), crate::SecurityError> {
let has_roles = self.check_roles(context).await;
let has_authorities = self.check_authorities(context).await;
if self.require_all {
if !self.roles.is_empty() && !has_roles {
return Err(crate::SecurityError::InsufficientPermissions {
required: self
.roles
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(", "),
has: "none".to_string(),
});
}
if !self.authorities.is_empty() && !has_authorities {
return Err(crate::SecurityError::InsufficientPermissions {
required: self
.authorities
.iter()
.map(Authority::authority)
.collect::<Vec<_>>()
.join(", "),
has: "none".to_string(),
});
}
} else {
if !has_roles
&& !has_authorities
&& (!self.roles.is_empty() || !self.authorities.is_empty())
{
return Err(crate::SecurityError::AccessDenied(
"Access denied: insufficient permissions".to_string(),
));
}
}
Ok(())
}
async fn check_roles(&self, context: &SecurityContext) -> bool {
if self.roles.is_empty() {
return true;
}
if self.require_all {
for role in &self.roles {
if !context.has_role(role).await {
return false;
}
}
true
} else {
for role in &self.roles {
if context.has_role(role).await {
return true;
}
}
false
}
}
async fn check_authorities(&self, context: &SecurityContext) -> bool {
if self.authorities.is_empty() {
return true;
}
if self.require_all {
for auth in &self.authorities {
if !context.has_authority(auth).await {
return false;
}
}
true
} else {
for auth in &self.authorities {
if context.has_authority(auth).await {
return true;
}
}
false
}
}
}
impl Default for SecurityMetadata {
fn default() -> Self {
Self::new()
}
}
pub trait Secured {
fn security_metadata(&self) -> SecurityMetadata {
SecurityMetadata::new()
}
}
impl<T> Secured for T where T: ?Sized {}
pub struct SecuredHelper;
impl SecuredHelper {
pub fn with_roles(roles: &[&str]) -> SecurityMetadata {
SecurityMetadata::new().add_roles(roles.iter().map(|r| Role::from_str(r)).collect())
}
pub fn with_authorities(authorities: &[Authority]) -> SecurityMetadata {
let mut metadata = SecurityMetadata::new();
for auth in authorities {
metadata = metadata.add_authority(auth.clone());
}
metadata
}
pub async fn check_access(
context: &SecurityContext,
metadata: &SecurityMetadata,
) -> Result<(), crate::SecurityError> {
metadata.check(context).await
}
}
pub struct Constraints;
impl Constraints {
pub fn admin_only() -> SecurityMetadata {
SecurityMetadata::new().add_role(Role::Admin)
}
pub fn user_or_admin() -> SecurityMetadata {
SecurityMetadata::new()
.add_roles(vec![Role::User, Role::Admin])
.require_all(false)
}
pub fn authenticated() -> SecurityMetadata {
SecurityMetadata::new()
}
pub fn permit_all() -> SecurityMetadata {
SecurityMetadata::new()
}
pub fn deny_all() -> SecurityMetadata {
SecurityMetadata::new()
.add_role(Role::Custom("IMPOSSIBLE_ROLE_TO_HAVE".to_string()))
.require_all(true)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_security_metadata() {
let metadata = SecurityMetadata::new()
.add_role(Role::Admin)
.add_role(Role::User)
.require_all(false);
assert_eq!(metadata.roles.len(), 2);
assert!(!metadata.require_all);
}
#[test]
fn test_constraints() {
let admin = Constraints::admin_only();
assert_eq!(admin.roles.len(), 1);
assert!(admin.roles.contains(&Role::Admin));
let permit = Constraints::permit_all();
assert!(permit.roles.is_empty());
}
}