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