webgates_core/authz/
authorization_service.rs1use crate::accounts::Account;
2use crate::authz::access_hierarchy::AccessHierarchy;
3use crate::authz::access_policy::AccessPolicy;
4
5use std::collections::HashSet;
6use tracing::debug;
7
8#[derive(Debug, Clone)]
14pub struct AuthorizationService<R, G>
15where
16 R: AccessHierarchy + Eq + std::fmt::Display,
17 G: Eq + Clone,
18{
19 policy: AccessPolicy<R, G>,
20}
21
22impl<R, G> AuthorizationService<R, G>
23where
24 R: AccessHierarchy + Eq + std::fmt::Display,
25 G: Eq + Clone,
26{
27 pub fn new(policy: AccessPolicy<R, G>) -> Self {
29 Self { policy }
30 }
31
32 pub fn is_authorized(&self, account: &Account<R, G>) -> bool {
40 self.meets_role_requirement(account)
41 || self.meets_role_hierarchy_requirement(account)
42 || self.meets_group_requirement(account)
43 || self.meets_permission_requirement(account)
44 }
45
46 pub fn meets_role_requirement(&self, account: &Account<R, G>) -> bool {
48 account.roles.iter().any(|role| {
49 self.policy
50 .role_requirements()
51 .iter()
52 .any(|scope| scope.grants_role(role))
53 })
54 }
55
56 pub fn meets_role_hierarchy_requirement(&self, account: &Account<R, G>) -> bool {
62 debug!("Checking role hierarchy requirements.");
63 account.roles.iter().any(|user_role| {
64 self.policy
65 .role_requirements()
66 .iter()
67 .any(|scope| scope.grants_supervisor(user_role))
68 })
69 }
70
71 pub fn meets_group_requirement(&self, account: &Account<R, G>) -> bool {
73 account.groups.iter().any(|group| {
74 self.policy
75 .group_requirements()
76 .iter()
77 .any(|required_group| required_group == group)
78 })
79 }
80
81 pub fn meets_permission_requirement(&self, account: &Account<R, G>) -> bool {
83 let account_permissions: HashSet<u64> = account.permissions.iter().collect();
84 let required_permissions: HashSet<u64> =
85 self.policy.permission_requirements().iter().collect();
86
87 !account_permissions.is_disjoint(&required_permissions)
88 }
89
90 pub fn policy_denies_all_access(&self) -> bool {
94 self.policy.denies_all()
95 }
96
97 pub fn clone_policy(&self) -> AccessPolicy<R, G> {
99 self.policy.clone()
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::AuthorizationService;
106 use crate::accounts::Account;
107 use crate::authz::access_policy::AccessPolicy;
108 use crate::groups::Group;
109 use crate::roles::Role;
110
111 fn create_test_account() -> Account<Role, Group> {
112 let mut account = Account::new("test_user");
113 account.roles = vec![Role::Admin];
114 account.groups = vec![Group::new("engineering")];
115 account.with_permissions(["read:api", "write:docs"].into_iter().collect())
116 }
117
118 #[test]
119 fn authorization_service_reports_empty_policy() {
120 let service: AuthorizationService<Role, Group> =
121 AuthorizationService::new(AccessPolicy::deny_all());
122
123 assert!(service.policy_denies_all_access());
124 }
125
126 #[test]
127 fn authorization_service_reports_non_empty_policy() {
128 let service: AuthorizationService<Role, Group> =
129 AuthorizationService::new(AccessPolicy::require_role(Role::Admin));
130
131 assert!(!service.policy_denies_all_access());
132 }
133
134 #[test]
135 fn meets_exact_role_requirement() {
136 let account = create_test_account();
137 let service = AuthorizationService::new(AccessPolicy::require_role(Role::Admin));
138
139 assert!(service.meets_role_requirement(&account));
140 }
141
142 #[test]
143 fn rejects_missing_exact_role_requirement() {
144 let account = create_test_account();
145 let service = AuthorizationService::new(AccessPolicy::require_role(Role::User));
146
147 assert!(!service.meets_role_requirement(&account));
148 }
149
150 #[test]
151 fn meets_group_requirement() {
152 let account = create_test_account();
153 let service =
154 AuthorizationService::new(AccessPolicy::require_group(Group::new("engineering")));
155
156 assert!(service.meets_group_requirement(&account));
157 }
158
159 #[test]
160 fn rejects_missing_group_requirement() {
161 let account = create_test_account();
162 let service = AuthorizationService::new(AccessPolicy::require_group(Group::new("sales")));
163
164 assert!(!service.meets_group_requirement(&account));
165 }
166
167 #[test]
168 fn meets_permission_requirement() {
169 let account = create_test_account();
170 let service = AuthorizationService::new(AccessPolicy::require_permission("read:api"));
171
172 assert!(service.meets_permission_requirement(&account));
173 }
174
175 #[test]
176 fn rejects_missing_permission_requirement() {
177 let account = create_test_account();
178 let service = AuthorizationService::new(AccessPolicy::require_permission("admin:system"));
179
180 assert!(!service.meets_permission_requirement(&account));
181 }
182
183 #[test]
184 fn meets_role_hierarchy_requirement_for_supervisor() {
185 let account = create_test_account();
186 let service =
187 AuthorizationService::new(AccessPolicy::require_role_or_supervisor(Role::Moderator));
188
189 assert!(service.meets_role_hierarchy_requirement(&account));
190 }
191
192 #[test]
193 fn rejects_role_hierarchy_requirement_when_supervisors_are_not_allowed() {
194 let account = create_test_account();
195 let service = AuthorizationService::new(AccessPolicy::require_role(Role::Moderator));
196
197 assert!(!service.meets_role_hierarchy_requirement(&account));
198 }
199
200 #[test]
201 fn is_authorized_when_any_requirement_matches() {
202 let account = create_test_account();
203 let service = AuthorizationService::new(
204 AccessPolicy::require_role(Role::User).or_require_group(Group::new("engineering")),
205 );
206
207 assert!(service.is_authorized(&account));
208 }
209
210 #[test]
211 fn is_not_authorized_when_nothing_matches() {
212 let account = create_test_account();
213 let service = AuthorizationService::new(
214 AccessPolicy::require_role(Role::User).or_require_group(Group::new("sales")),
215 );
216
217 assert!(!service.is_authorized(&account));
218 }
219}