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