mod routes;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use crate::error::InternalError;
#[cfg(feature = "authorization-handler-rbac")]
use crate::rest_api::auth::authorization::rbac::store::{
Identity as RBACIdentity, RoleBasedAuthorizationStore, ADMIN_ROLE_ID,
};
use crate::rest_api::auth::identity::Identity;
use super::{AuthorizationHandler, AuthorizationHandlerResult};
#[derive(Clone, Default)]
pub struct MaintenanceModeAuthorizationHandler {
maintenance_mode: Arc<AtomicBool>,
#[cfg(feature = "authorization-handler-rbac")]
rbac_store: Option<Box<dyn RoleBasedAuthorizationStore>>,
}
impl MaintenanceModeAuthorizationHandler {
pub fn new(rbac_store: Option<Box<dyn RoleBasedAuthorizationStore>>) -> Self {
Self {
rbac_store,
..Default::default()
}
}
pub fn is_maintenance_mode_enabled(&self) -> bool {
self.maintenance_mode.load(Ordering::Relaxed)
}
pub fn set_maintenance_mode(&self, maintenance_mode: bool) {
self.maintenance_mode
.store(maintenance_mode, Ordering::Relaxed);
}
}
impl AuthorizationHandler for MaintenanceModeAuthorizationHandler {
fn has_permission(
&self,
#[allow(unused_variables)] identity: &Identity,
permission_id: &str,
) -> Result<AuthorizationHandlerResult, InternalError> {
if !permission_id.ends_with(".read") && self.maintenance_mode.load(Ordering::Relaxed) {
#[cfg(feature = "authorization-handler-rbac")]
{
let is_admin = self
.rbac_store
.as_ref()
.and_then(|store| {
let rbac_identity: Option<RBACIdentity> = identity.into();
Some(
store
.get_assignment(&rbac_identity?)
.ok()??
.roles()
.iter()
.any(|role| role == ADMIN_ROLE_ID),
)
})
.unwrap_or(false);
if is_admin {
return Ok(AuthorizationHandlerResult::Continue);
}
}
Ok(AuthorizationHandlerResult::Deny)
} else {
Ok(AuthorizationHandlerResult::Continue)
}
}
fn clone_box(&self) -> Box<dyn AuthorizationHandler> {
Box::new(self.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rest_api::auth::authorization::rbac::store::{
Assignment, AssignmentBuilder, Role, RoleBasedAuthorizationStore,
RoleBasedAuthorizationStoreError,
};
const ADMIN_USER_IDENTITY: &str = "admin_user";
const NON_ADMIN_USER_IDENTITY: &str = "non_admin_user";
#[test]
fn auth_handler_read_permissions() {
let handler = MaintenanceModeAuthorizationHandler::default();
assert_eq!(handler.is_maintenance_mode_enabled(), false);
assert!(matches!(
handler.has_permission(&Identity::Custom("identity".into()), "permission.read"),
Ok(AuthorizationHandlerResult::Continue)
));
handler.set_maintenance_mode(true);
assert_eq!(handler.is_maintenance_mode_enabled(), true);
assert!(matches!(
handler.has_permission(&Identity::Custom("identity".into()), "permission.read"),
Ok(AuthorizationHandlerResult::Continue)
));
}
#[test]
fn auth_handler_non_read_permissions() {
let handler = MaintenanceModeAuthorizationHandler::default();
assert_eq!(handler.is_maintenance_mode_enabled(), false);
assert!(matches!(
handler.has_permission(&Identity::Custom("identity".into()), "permission"),
Ok(AuthorizationHandlerResult::Continue)
));
handler.set_maintenance_mode(true);
assert_eq!(handler.is_maintenance_mode_enabled(), true);
assert!(matches!(
handler.has_permission(&Identity::Custom("identity".into()), "permission"),
Ok(AuthorizationHandlerResult::Deny)
));
handler.set_maintenance_mode(false);
assert_eq!(handler.is_maintenance_mode_enabled(), false);
assert!(matches!(
handler.has_permission(&Identity::Custom("identity".into()), "permission"),
Ok(AuthorizationHandlerResult::Continue)
));
}
#[cfg(feature = "authorization-handler-rbac")]
#[test]
fn auth_handler_rbac_admin() {
let handler = MaintenanceModeAuthorizationHandler::new(Some(Box::new(
MockRoleBasedAuthorizationStore,
)));
handler.set_maintenance_mode(true);
assert_eq!(handler.is_maintenance_mode_enabled(), true);
assert!(matches!(
handler.has_permission(&Identity::User(ADMIN_USER_IDENTITY.into()), "permission"),
Ok(AuthorizationHandlerResult::Continue)
));
assert!(matches!(
handler.has_permission(
&Identity::User(NON_ADMIN_USER_IDENTITY.into()),
"permission"
),
Ok(AuthorizationHandlerResult::Deny)
));
assert!(matches!(
handler.has_permission(&Identity::User("unknown".into()), "permission"),
Ok(AuthorizationHandlerResult::Deny)
));
}
#[derive(Clone)]
struct MockRoleBasedAuthorizationStore;
impl RoleBasedAuthorizationStore for MockRoleBasedAuthorizationStore {
fn get_role(&self, _id: &str) -> Result<Option<Role>, RoleBasedAuthorizationStoreError> {
unimplemented!()
}
fn list_roles(
&self,
) -> Result<Box<dyn ExactSizeIterator<Item = Role>>, RoleBasedAuthorizationStoreError>
{
unimplemented!()
}
fn add_role(&self, _role: Role) -> Result<(), RoleBasedAuthorizationStoreError> {
unimplemented!()
}
fn update_role(&self, _role: Role) -> Result<(), RoleBasedAuthorizationStoreError> {
unimplemented!()
}
fn remove_role(&self, _role_id: &str) -> Result<(), RoleBasedAuthorizationStoreError> {
unimplemented!()
}
fn get_assignment(
&self,
identity: &RBACIdentity,
) -> Result<Option<Assignment>, RoleBasedAuthorizationStoreError> {
let admin_identity = RBACIdentity::User(ADMIN_USER_IDENTITY.into());
if identity == &admin_identity {
return Ok(Some(
AssignmentBuilder::new()
.with_identity(admin_identity)
.with_roles(vec![ADMIN_ROLE_ID.into()])
.build()?,
));
}
let non_admin_identity = RBACIdentity::User(NON_ADMIN_USER_IDENTITY.into());
if identity == &non_admin_identity {
return Ok(Some(
AssignmentBuilder::new()
.with_identity(non_admin_identity)
.with_roles(vec!["other".into()])
.build()?,
));
}
Ok(None)
}
fn get_assigned_roles(
&self,
_identity: &RBACIdentity,
) -> Result<Box<dyn ExactSizeIterator<Item = Role>>, RoleBasedAuthorizationStoreError>
{
unimplemented!()
}
fn list_assignments(
&self,
) -> Result<Box<dyn ExactSizeIterator<Item = Assignment>>, RoleBasedAuthorizationStoreError>
{
unimplemented!()
}
fn add_assignment(
&self,
_assignment: Assignment,
) -> Result<(), RoleBasedAuthorizationStoreError> {
unimplemented!()
}
fn update_assignment(
&self,
_assignment: Assignment,
) -> Result<(), RoleBasedAuthorizationStoreError> {
unimplemented!()
}
fn remove_assignment(
&self,
_identity: &RBACIdentity,
) -> Result<(), RoleBasedAuthorizationStoreError> {
unimplemented!()
}
fn clone_box(&self) -> Box<dyn RoleBasedAuthorizationStore> {
Box::new(self.clone())
}
}
}