axum_gate/authz/
authorization_service.rs1use super::{AccessHierarchy, AccessPolicy};
2use crate::accounts::Account;
3
4use tracing::debug;
5
6#[derive(Debug, Clone)]
12pub struct AuthorizationService<R, G>
13where
14 R: AccessHierarchy + Eq + std::fmt::Display,
15 G: Eq + Clone,
16{
17 policy: AccessPolicy<R, G>,
18}
19
20impl<R, G> AuthorizationService<R, G>
21where
22 R: AccessHierarchy + Eq + std::fmt::Display,
23 G: Eq + Clone,
24{
25 pub fn new(policy: AccessPolicy<R, G>) -> Self {
27 Self { policy }
28 }
29
30 pub fn is_authorized(&self, account: &Account<R, G>) -> bool {
38 self.meets_permission_requirement(account)
39 || self.meets_group_requirement(account)
40 || self.meets_role_requirement(account)
41 || self.meets_role_hierarchy_requirement(account)
42 }
43
44 pub fn meets_role_requirement(&self, account: &Account<R, G>) -> bool {
46 account.roles.iter().any(|r| {
47 self.policy
48 .role_requirements()
49 .iter()
50 .any(|scope| scope.grants_role(r))
51 })
52 }
53
54 pub fn meets_role_hierarchy_requirement(&self, account: &Account<R, G>) -> bool {
59 debug!("Checking role hierarchy (same-or-supervisor) against required roles.");
60 account.roles.iter().any(|user_role| {
61 self.policy
62 .role_requirements()
63 .iter()
64 .any(|scope| scope.grants_supervisor(user_role))
65 })
66 }
67
68 pub fn meets_group_requirement(&self, account: &Account<R, G>) -> bool {
70 account.groups.iter().any(|r| {
71 self.policy
72 .group_requirements()
73 .iter()
74 .any(|g_scope| g_scope.eq(r))
75 })
76 }
77
78 pub fn meets_permission_requirement(&self, account: &Account<R, G>) -> bool {
80 let account_permissions: std::collections::HashSet<u64> =
82 account.permissions.iter().collect();
83 let required_permissions: std::collections::HashSet<u64> =
84 self.policy.permission_requirements().iter().collect();
85
86 !account_permissions.is_disjoint(&required_permissions)
87 }
88
89 pub fn policy_denies_all_access(&self) -> bool {
91 self.policy.denies_all()
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 use crate::groups::Group;
100 use crate::roles::Role;
101
102 fn create_test_account() -> Account<Role, Group> {
103 use crate::permissions::Permissions;
104 use uuid::Uuid;
105
106 let mut permissions = Permissions::new();
107 permissions.bitmap_mut().insert(1u64);
109 permissions.bitmap_mut().insert(5u64);
110
111 Account {
112 account_id: Uuid::new_v4(),
113 user_id: "test_user".to_string(),
114 roles: vec![Role::Admin],
115 groups: vec![Group::new("engineering")],
116 permissions,
117 }
118 }
119
120 #[test]
121 fn authorization_service_empty_criteria() {
122 let service: AuthorizationService<Role, Group> =
123 AuthorizationService::new(AccessPolicy::deny_all());
124 assert!(service.policy_denies_all_access());
125 }
126
127 #[test]
128 fn authorization_service_non_empty_criteria() {
129 let policy = AccessPolicy::require_role(Role::Admin);
130 let service: AuthorizationService<Role, Group> = AuthorizationService::new(policy);
131 assert!(!service.policy_denies_all_access());
132 }
133
134 #[test]
135 fn authorized_by_role_matching() {
136 let account = create_test_account();
137 let policy = AccessPolicy::require_role(Role::Admin);
138 let service = AuthorizationService::new(policy);
139
140 assert!(service.meets_role_requirement(&account));
141 }
142
143 #[test]
144 fn authorized_by_role_not_matching() {
145 let account = create_test_account();
146 let policy = AccessPolicy::require_role(Role::User);
147 let service = AuthorizationService::new(policy);
148
149 assert!(!service.meets_role_requirement(&account));
150 }
151
152 #[test]
153 fn authorized_by_group_matching() {
154 let account = create_test_account();
155 let policy = AccessPolicy::require_group(Group::new("engineering"));
156 let service = AuthorizationService::new(policy);
157
158 assert!(service.meets_group_requirement(&account));
159 }
160
161 #[test]
162 fn authorized_by_group_not_matching() {
163 let account = create_test_account();
164 let policy = AccessPolicy::require_group(Group::new("sales"));
165 let service = AuthorizationService::new(policy);
166
167 assert!(!service.meets_group_requirement(&account));
168 }
169
170 #[test]
171 fn authorized_by_permission_matching() {
172 let account = create_test_account();
173 let policy =
174 AccessPolicy::require_permission(crate::permissions::PermissionId::from_u64(1)); let service = AuthorizationService::new(policy);
176
177 assert!(service.meets_permission_requirement(&account));
178 }
179
180 #[test]
181 fn authorized_by_permission_not_matching() {
182 let account = create_test_account();
183 let policy =
184 AccessPolicy::require_permission(crate::permissions::PermissionId::from_u64(10)); let service = AuthorizationService::new(policy);
186
187 assert!(!service.meets_permission_requirement(&account));
188 }
189
190 #[test]
191 fn is_authorized_returns_true_when_any_criteria_match() {
192 let account = create_test_account();
193 let policy = AccessPolicy::require_role(Role::User) .or_require_group(Group::new("engineering")); let service = AuthorizationService::new(policy);
196
197 assert!(service.is_authorized(&account));
198 }
199
200 #[test]
201 fn is_authorized_returns_false_when_no_criteria_match() {
202 let account = create_test_account();
203 let policy = AccessPolicy::require_role(Role::User) .or_require_group(Group::new("sales")); let service = AuthorizationService::new(policy);
206
207 assert!(!service.is_authorized(&account));
208 }
209}