1use crate::error::{Error, Result};
38use crate::orm::Db;
39
40use super::role::Role;
41use super::users::{would_orphan_protected, Identity};
42
43pub fn enforce_self_demote_safe(
49 actor: &Identity,
50 target_id: i64,
51 new_role: Role,
52 new_active: bool,
53) -> Result<()> {
54 if actor.user_id != target_id {
55 return Ok(());
56 }
57 if !new_active {
58 return Err(Error::Forbidden(
59 "You cannot deactivate yourself.".to_string(),
60 ));
61 }
62 if new_role.rank() < actor.role.rank() {
63 return Err(Error::Forbidden(
64 "You cannot demote yourself below your current authority level.".to_string(),
65 ));
66 }
67 Ok(())
68}
69
70pub fn enforce_cross_rank_safe(actor: &Identity, target_id: i64, target_role: Role) -> Result<()> {
75 if actor.user_id == target_id {
76 return Ok(());
77 }
78 if target_role.rank() >= actor.role.rank() {
79 return Err(Error::Forbidden(
80 "You cannot modify users at or above your authority level.".to_string(),
81 ));
82 }
83 Ok(())
84}
85
86pub fn enforce_role_ceiling(actor: &Identity, requested_role: Role) -> Result<()> {
92 if requested_role.rank() > actor.role.rank() {
93 return Err(Error::Forbidden(
94 "You cannot assign a role higher than your own authority.".to_string(),
95 ));
96 }
97 Ok(())
98}
99
100pub async fn enforce_no_orphan_role(
105 db: &Db,
106 target_id: i64,
107 new_role: Role,
108 new_active: bool,
109) -> Result<()> {
110 if let Some(role) = would_orphan_protected(db, target_id, new_role, new_active).await? {
111 return Err(Error::Forbidden(format!(
112 "At least one active {} must remain.",
113 role.label()
114 )));
115 }
116 Ok(())
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 fn ident(role: Role, user_id: i64) -> Identity {
124 Identity {
125 user_id,
126 email: format!("u{user_id}@test"),
127 role,
128 is_active: true,
129 is_demo: false,
130 demo_label: None,
131 must_change_password: false,
132 mfa_enabled: false,
133 trust_level: crate::auth::SessionTrust::Authenticated,
134 }
135 }
136
137 #[test]
140 fn self_demote_blocks_role_drop() {
141 let actor = ident(Role::Administrator, 1);
142 let err = enforce_self_demote_safe(&actor, 1, Role::Staff, true).unwrap_err();
143 assert!(matches!(err, Error::Forbidden(_)));
144 }
145
146 #[test]
147 fn self_demote_blocks_self_deactivate() {
148 let actor = ident(Role::Administrator, 1);
149 let err = enforce_self_demote_safe(&actor, 1, Role::Administrator, false).unwrap_err();
150 assert!(matches!(err, Error::Forbidden(_)));
151 }
152
153 #[test]
154 fn self_demote_allows_self_keep_rank() {
155 let actor = ident(Role::Administrator, 1);
156 assert!(enforce_self_demote_safe(&actor, 1, Role::Administrator, true).is_ok());
157 }
158
159 #[test]
160 fn self_demote_ignores_other_targets() {
161 let actor = ident(Role::Administrator, 1);
162 assert!(enforce_self_demote_safe(&actor, 2, Role::User, true).is_ok());
165 }
166
167 #[test]
170 fn cross_rank_blocks_editing_higher() {
171 let actor = ident(Role::Administrator, 1);
172 let err = enforce_cross_rank_safe(&actor, 2, Role::Developer).unwrap_err();
173 assert!(matches!(err, Error::Forbidden(_)));
174 }
175
176 #[test]
177 fn cross_rank_blocks_editing_equal() {
178 let actor = ident(Role::Administrator, 1);
179 let err = enforce_cross_rank_safe(&actor, 2, Role::Administrator).unwrap_err();
180 assert!(matches!(err, Error::Forbidden(_)));
181 }
182
183 #[test]
184 fn cross_rank_allows_editing_lower() {
185 let actor = ident(Role::Administrator, 1);
186 assert!(enforce_cross_rank_safe(&actor, 2, Role::Staff).is_ok());
187 assert!(enforce_cross_rank_safe(&actor, 2, Role::Supervisor).is_ok());
188 }
189
190 #[test]
191 fn cross_rank_allows_editing_self() {
192 let actor = ident(Role::Administrator, 1);
193 assert!(enforce_cross_rank_safe(&actor, 1, Role::Administrator).is_ok());
195 }
196
197 #[test]
198 fn cross_rank_developer_can_edit_administrator() {
199 let actor = ident(Role::Developer, 1);
200 assert!(enforce_cross_rank_safe(&actor, 2, Role::Administrator).is_ok());
201 }
202
203 #[test]
206 fn ceiling_blocks_promote_above_self() {
207 let actor = ident(Role::Administrator, 1);
208 let err = enforce_role_ceiling(&actor, Role::Developer).unwrap_err();
209 assert!(matches!(err, Error::Forbidden(_)));
210 }
211
212 #[test]
213 fn ceiling_allows_assigning_equal_or_below() {
214 let actor = ident(Role::Administrator, 1);
215 assert!(enforce_role_ceiling(&actor, Role::Administrator).is_ok());
216 assert!(enforce_role_ceiling(&actor, Role::Supervisor).is_ok());
217 assert!(enforce_role_ceiling(&actor, Role::Staff).is_ok());
218 assert!(enforce_role_ceiling(&actor, Role::User).is_ok());
219 }
220
221 #[test]
222 fn ceiling_supervisor_cannot_create_administrator() {
223 let actor = ident(Role::Supervisor, 1);
224 let err = enforce_role_ceiling(&actor, Role::Administrator).unwrap_err();
225 assert!(matches!(err, Error::Forbidden(_)));
226 }
227
228 #[test]
229 fn ceiling_developer_can_assign_anything_inclusive() {
230 let actor = ident(Role::Developer, 1);
231 for r in [
232 Role::User,
233 Role::Staff,
234 Role::Supervisor,
235 Role::Administrator,
236 Role::Developer,
237 ] {
238 assert!(enforce_role_ceiling(&actor, r).is_ok());
239 }
240 }
241}