rustio_admin/auth/
guards.rs1use 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 }
129 }
130
131 #[test]
134 fn self_demote_blocks_role_drop() {
135 let actor = ident(Role::Administrator, 1);
136 let err = enforce_self_demote_safe(&actor, 1, Role::Staff, true).unwrap_err();
137 assert!(matches!(err, Error::Forbidden(_)));
138 }
139
140 #[test]
141 fn self_demote_blocks_self_deactivate() {
142 let actor = ident(Role::Administrator, 1);
143 let err = enforce_self_demote_safe(&actor, 1, Role::Administrator, false).unwrap_err();
144 assert!(matches!(err, Error::Forbidden(_)));
145 }
146
147 #[test]
148 fn self_demote_allows_self_keep_rank() {
149 let actor = ident(Role::Administrator, 1);
150 assert!(enforce_self_demote_safe(&actor, 1, Role::Administrator, true).is_ok());
151 }
152
153 #[test]
154 fn self_demote_ignores_other_targets() {
155 let actor = ident(Role::Administrator, 1);
156 assert!(enforce_self_demote_safe(&actor, 2, Role::User, true).is_ok());
159 }
160
161 #[test]
164 fn cross_rank_blocks_editing_higher() {
165 let actor = ident(Role::Administrator, 1);
166 let err = enforce_cross_rank_safe(&actor, 2, Role::Developer).unwrap_err();
167 assert!(matches!(err, Error::Forbidden(_)));
168 }
169
170 #[test]
171 fn cross_rank_blocks_editing_equal() {
172 let actor = ident(Role::Administrator, 1);
173 let err = enforce_cross_rank_safe(&actor, 2, Role::Administrator).unwrap_err();
174 assert!(matches!(err, Error::Forbidden(_)));
175 }
176
177 #[test]
178 fn cross_rank_allows_editing_lower() {
179 let actor = ident(Role::Administrator, 1);
180 assert!(enforce_cross_rank_safe(&actor, 2, Role::Staff).is_ok());
181 assert!(enforce_cross_rank_safe(&actor, 2, Role::Supervisor).is_ok());
182 }
183
184 #[test]
185 fn cross_rank_allows_editing_self() {
186 let actor = ident(Role::Administrator, 1);
187 assert!(enforce_cross_rank_safe(&actor, 1, Role::Administrator).is_ok());
189 }
190
191 #[test]
192 fn cross_rank_developer_can_edit_administrator() {
193 let actor = ident(Role::Developer, 1);
194 assert!(enforce_cross_rank_safe(&actor, 2, Role::Administrator).is_ok());
195 }
196
197 #[test]
200 fn ceiling_blocks_promote_above_self() {
201 let actor = ident(Role::Administrator, 1);
202 let err = enforce_role_ceiling(&actor, Role::Developer).unwrap_err();
203 assert!(matches!(err, Error::Forbidden(_)));
204 }
205
206 #[test]
207 fn ceiling_allows_assigning_equal_or_below() {
208 let actor = ident(Role::Administrator, 1);
209 assert!(enforce_role_ceiling(&actor, Role::Administrator).is_ok());
210 assert!(enforce_role_ceiling(&actor, Role::Supervisor).is_ok());
211 assert!(enforce_role_ceiling(&actor, Role::Staff).is_ok());
212 assert!(enforce_role_ceiling(&actor, Role::User).is_ok());
213 }
214
215 #[test]
216 fn ceiling_supervisor_cannot_create_administrator() {
217 let actor = ident(Role::Supervisor, 1);
218 let err = enforce_role_ceiling(&actor, Role::Administrator).unwrap_err();
219 assert!(matches!(err, Error::Forbidden(_)));
220 }
221
222 #[test]
223 fn ceiling_developer_can_assign_anything_inclusive() {
224 let actor = ident(Role::Developer, 1);
225 for r in [
226 Role::User,
227 Role::Staff,
228 Role::Supervisor,
229 Role::Administrator,
230 Role::Developer,
231 ] {
232 assert!(enforce_role_ceiling(&actor, r).is_ok());
233 }
234 }
235}