use crate::error::{Error, Result};
use crate::orm::Db;
use super::role::Role;
use super::users::{would_orphan_protected, Identity};
pub fn enforce_self_demote_safe(
actor: &Identity,
target_id: i64,
new_role: Role,
new_active: bool,
) -> Result<()> {
if actor.user_id != target_id {
return Ok(());
}
if !new_active {
return Err(Error::Forbidden(
"You cannot deactivate yourself.".to_string(),
));
}
if new_role.rank() < actor.role.rank() {
return Err(Error::Forbidden(
"You cannot demote yourself below your current authority level.".to_string(),
));
}
Ok(())
}
pub fn enforce_cross_rank_safe(actor: &Identity, target_id: i64, target_role: Role) -> Result<()> {
if actor.user_id == target_id {
return Ok(());
}
if target_role.rank() >= actor.role.rank() {
return Err(Error::Forbidden(
"You cannot modify users at or above your authority level.".to_string(),
));
}
Ok(())
}
pub fn enforce_role_ceiling(actor: &Identity, requested_role: Role) -> Result<()> {
if requested_role.rank() > actor.role.rank() {
return Err(Error::Forbidden(
"You cannot assign a role higher than your own authority.".to_string(),
));
}
Ok(())
}
pub async fn enforce_no_orphan_role(
db: &Db,
target_id: i64,
new_role: Role,
new_active: bool,
) -> Result<()> {
if let Some(role) = would_orphan_protected(db, target_id, new_role, new_active).await? {
return Err(Error::Forbidden(format!(
"At least one active {} must remain.",
role.label()
)));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn ident(role: Role, user_id: i64) -> Identity {
Identity {
user_id,
email: format!("u{user_id}@test"),
role,
is_active: true,
is_demo: false,
demo_label: None,
must_change_password: false,
mfa_enabled: false,
trust_level: crate::auth::SessionTrust::Authenticated,
}
}
#[test]
fn self_demote_blocks_role_drop() {
let actor = ident(Role::Administrator, 1);
let err = enforce_self_demote_safe(&actor, 1, Role::Staff, true).unwrap_err();
assert!(matches!(err, Error::Forbidden(_)));
}
#[test]
fn self_demote_blocks_self_deactivate() {
let actor = ident(Role::Administrator, 1);
let err = enforce_self_demote_safe(&actor, 1, Role::Administrator, false).unwrap_err();
assert!(matches!(err, Error::Forbidden(_)));
}
#[test]
fn self_demote_allows_self_keep_rank() {
let actor = ident(Role::Administrator, 1);
assert!(enforce_self_demote_safe(&actor, 1, Role::Administrator, true).is_ok());
}
#[test]
fn self_demote_ignores_other_targets() {
let actor = ident(Role::Administrator, 1);
assert!(enforce_self_demote_safe(&actor, 2, Role::User, true).is_ok());
}
#[test]
fn cross_rank_blocks_editing_higher() {
let actor = ident(Role::Administrator, 1);
let err = enforce_cross_rank_safe(&actor, 2, Role::Developer).unwrap_err();
assert!(matches!(err, Error::Forbidden(_)));
}
#[test]
fn cross_rank_blocks_editing_equal() {
let actor = ident(Role::Administrator, 1);
let err = enforce_cross_rank_safe(&actor, 2, Role::Administrator).unwrap_err();
assert!(matches!(err, Error::Forbidden(_)));
}
#[test]
fn cross_rank_allows_editing_lower() {
let actor = ident(Role::Administrator, 1);
assert!(enforce_cross_rank_safe(&actor, 2, Role::Staff).is_ok());
assert!(enforce_cross_rank_safe(&actor, 2, Role::Supervisor).is_ok());
}
#[test]
fn cross_rank_allows_editing_self() {
let actor = ident(Role::Administrator, 1);
assert!(enforce_cross_rank_safe(&actor, 1, Role::Administrator).is_ok());
}
#[test]
fn cross_rank_developer_can_edit_administrator() {
let actor = ident(Role::Developer, 1);
assert!(enforce_cross_rank_safe(&actor, 2, Role::Administrator).is_ok());
}
#[test]
fn ceiling_blocks_promote_above_self() {
let actor = ident(Role::Administrator, 1);
let err = enforce_role_ceiling(&actor, Role::Developer).unwrap_err();
assert!(matches!(err, Error::Forbidden(_)));
}
#[test]
fn ceiling_allows_assigning_equal_or_below() {
let actor = ident(Role::Administrator, 1);
assert!(enforce_role_ceiling(&actor, Role::Administrator).is_ok());
assert!(enforce_role_ceiling(&actor, Role::Supervisor).is_ok());
assert!(enforce_role_ceiling(&actor, Role::Staff).is_ok());
assert!(enforce_role_ceiling(&actor, Role::User).is_ok());
}
#[test]
fn ceiling_supervisor_cannot_create_administrator() {
let actor = ident(Role::Supervisor, 1);
let err = enforce_role_ceiling(&actor, Role::Administrator).unwrap_err();
assert!(matches!(err, Error::Forbidden(_)));
}
#[test]
fn ceiling_developer_can_assign_anything_inclusive() {
let actor = ident(Role::Developer, 1);
for r in [
Role::User,
Role::Staff,
Role::Supervisor,
Role::Administrator,
Role::Developer,
] {
assert!(enforce_role_ceiling(&actor, r).is_ok());
}
}
}