use crate::request::ReqCtx;
use once_cell::sync::OnceCell;
use std::sync::Once;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Action<'a> {
List,
Read,
Create,
Update,
Delete,
Export,
Custom(&'a str),
}
impl<'a> Action<'a> {
pub fn as_str(&self) -> &'a str {
match self {
Action::List => "list",
Action::Read => "read",
Action::Create => "create",
Action::Update => "update",
Action::Delete => "delete",
Action::Export => "export",
Action::Custom(name) => name,
}
}
}
pub trait Authorizer: Send + Sync {
fn can(&self, roles: &[String], resource: &str, action: &Action<'_>) -> bool;
}
static AUTHORIZER: OnceCell<Box<dyn Authorizer>> = OnceCell::new();
pub fn set_authorizer(authorizer: Box<dyn Authorizer>) {
if AUTHORIZER.set(authorizer).is_err() {
tracing::warn!("adminx authorizer already initialized; ignoring reset");
}
}
pub fn authorizer() -> Option<&'static dyn Authorizer> {
AUTHORIZER.get().map(|b| b.as_ref())
}
static UNCONFIGURED_WARNED: Once = Once::new();
pub fn authorize(
ctx: &ReqCtx,
allowed_roles: &[String],
resource: &str,
action: Action<'_>,
) -> bool {
if !crate::auth::is_configured() {
UNCONFIGURED_WARNED.call_once(|| {
tracing::warn!(
"adminx: authentication is NOT configured — every page and API route is \
PUBLIC and RBAC is bypassed. Call `configure_auth(..)` (and seed an admin) \
to secure the panel. This warning is shown once."
);
});
return true;
}
if crate::auth::mfa_pending(ctx) {
return false;
}
let roles = ctx.roles();
match authorizer() {
Some(a) => a.can(&roles, resource, &action),
None => allowed_roles.iter().any(|r| roles.contains(r)),
}
}