potree_auth/authorization/adapters/
basic_authorization.rs1use super::super::domain::action::Action;
7use super::super::domain::error::AuthorizationEngineError;
8use super::super::domain::resource::Resource;
9use super::super::domain::resource::ResourceInstance;
10use super::super::ports::authorization_engine::AuthorizationEngine;
11use crate::common::domain::resource_type;
12use crate::user::domain::User;
13
14#[derive(Debug, Clone)]
16pub struct SimpleAuthorizationEngine;
17
18impl SimpleAuthorizationEngine {
19 #[tracing::instrument(
20 name = "`simple_authorization_engine`: evaluating on resource type",
21 err
22 )]
23 pub fn can_on_type(
24 &self,
25 user: &Option<User>,
26 action: &Action,
27 resource: &dyn Resource,
28 ) -> Result<(), AuthorizationEngineError> {
29 let Some(user) = user else {
31 return Err(AuthorizationEngineError::NotAuthenticated);
32 };
33
34 if user.is_admin() {
36 return Ok(());
37 };
38
39 match (action, resource.resource_type().as_str()) {
40 (&Action::List, resource_type::PROJECT) => Ok(()),
41 (&Action::Read, resource_type::PROJECTS_DASHBOARD) => Ok(()),
42 _ => Err(AuthorizationEngineError::NotAuthorized {
43 user: Box::new(user.clone()),
44 action: action.clone(),
45 resource_identifier: None,
46 resource_type: resource.resource_type(),
47 }),
48 }
49 }
50
51 #[tracing::instrument(
52 name = "`simple_authorization_engine`: evaluating on resource instance",
53 err
54 )]
55 pub fn can_on_instance(
56 &self,
57 user: &Option<User>,
58 action: &Action,
59 resource: &dyn ResourceInstance,
60 ) -> Result<(), AuthorizationEngineError> {
61 let Some(user) = user else {
63 return Err(AuthorizationEngineError::NotAuthenticated);
64 };
65
66 match action {
68 &Action::Read => {
69 if user.is_admin()
70 || resource.groups().map_or(false, |groups| {
71 groups.iter().any(|group| user.groups.contains(group))
72 })
73 {
74 Ok(())
75 } else {
76 Err(AuthorizationEngineError::NotAuthorized {
77 user: Box::new(user.clone()),
78 action: action.clone(),
79 resource_identifier: Some(resource.resource_identifier()),
80 resource_type: resource.resource_type(),
81 })
82 }
83 }
84 _ => Err(AuthorizationEngineError::NotAuthorized {
85 user: Box::new(user.clone()),
86 action: action.clone(),
87 resource_identifier: Some(resource.resource_identifier()),
88 resource_type: resource.resource_type(),
89 }),
90 }
91 }
92}
93
94impl AuthorizationEngine for SimpleAuthorizationEngine {
95 fn can_on_type(
96 &self,
97 user: &Option<User>,
98 action: &Action,
99 resource: &dyn Resource,
100 ) -> Result<(), AuthorizationEngineError> {
101 Self::can_on_type(self, user, action, resource)
102 }
103
104 fn can_on_instance(
105 &self,
106 user: &Option<User>,
107 action: &Action,
108 resource: &dyn ResourceInstance,
109 ) -> Result<(), AuthorizationEngineError> {
110 Self::can_on_instance(self, user, action, resource)
111 }
112}
113
114#[cfg(test)]
115mod authorization_service_tests {
116 use fake::Fake;
117 use fake::Faker;
118
119 use super::super::super::domain::resource::mocked_resource::MockedResource;
120 use super::*;
121 use crate::common::domain::group::Group;
122
123 mod can_on_type {
124
125 use super::*;
126 use crate::authorization::domain::resource::ResourceType;
127
128 #[test]
129 fn should_return_ok_if_the_user_is_an_admin() {
130 let authorization_service = SimpleAuthorizationEngine;
132
133 let user = User::dummy_admin();
134 let resource = Faker.fake::<MockedResource>();
135
136 let res = authorization_service.can_on_type(&Some(user), &Action::Read, &resource);
138
139 assert!(res.is_ok())
141 }
142
143 #[test]
144 fn should_return_ok_if_listing_projects() {
145 let authorization_service = SimpleAuthorizationEngine;
147
148 let user = Faker.fake::<User>();
149 let resource = MockedResource {
150 resource_type: ResourceType::new(resource_type::PROJECT.to_owned()),
151 ..Faker.fake()
152 };
153 let action = Action::List;
154
155 let res = authorization_service.can_on_type(&Some(user), &action, &resource);
157
158 assert!(res.is_ok())
160 }
161
162 #[test_case::test_matrix(
163 [&Action::Create, &Action::Read, &Action::Update, &Action::Delete]
164 )]
165 fn should_return_err_if_not_listing_projects(action: &Action) {
166 let authorization_service = SimpleAuthorizationEngine;
168
169 let user = Faker.fake::<User>();
170 let resource = MockedResource {
171 resource_type: ResourceType::new(resource_type::PROJECT.to_owned()),
172 ..Faker.fake()
173 };
174
175 let res = authorization_service.can_on_type(&Some(user), action, &resource);
177
178 assert!(matches!(
180 res,
181 Err(AuthorizationEngineError::NotAuthorized { .. })
182 ))
183 }
184
185 #[test]
186 fn should_return_ok_if_reading_a_project_dashboard() {
187 let authorization_service = SimpleAuthorizationEngine;
189
190 let user = Faker.fake::<User>();
191 let resource = MockedResource {
192 resource_type: ResourceType::new(resource_type::PROJECTS_DASHBOARD.to_owned()),
193 ..Faker.fake()
194 };
195 let action = Action::Read;
196
197 let res = authorization_service.can_on_type(&Some(user), &action, &resource);
199
200 assert!(res.is_ok())
202 }
203
204 #[test_case::test_matrix(
205 [&Action::Create, &Action::List, &Action::Update, &Action::Delete]
206 )]
207 fn should_return_err_if_not_reading_a_project_dashboard(action: &Action) {
208 let authorization_service = SimpleAuthorizationEngine;
210
211 let user = Faker.fake::<User>();
212 let resource = MockedResource {
213 resource_type: ResourceType::new(resource_type::PROJECTS_DASHBOARD.to_owned()),
214 ..Faker.fake()
215 };
216
217 let res = authorization_service.can_on_type(&Some(user), action, &resource);
219
220 assert!(matches!(
222 res,
223 Err(AuthorizationEngineError::NotAuthorized { .. })
224 ))
225 }
226
227 #[test]
228 fn should_return_err_for_other_combinations() {
229 let authorization_service = SimpleAuthorizationEngine;
231
232 let user = Faker.fake::<User>();
233 let resource = Faker.fake::<MockedResource>();
234 let action = Faker.fake::<Action>();
235
236 let res = authorization_service.can_on_type(&Some(user), &action, &resource);
238
239 assert!(matches!(
241 res,
242 Err(AuthorizationEngineError::NotAuthorized { .. })
243 ))
244 }
245 }
246
247 mod can_on_instance {
248
249 use super::*;
250
251 #[test]
252 fn should_return_ok_if_the_user_is_an_admin() {
253 let authorization_service = SimpleAuthorizationEngine;
255
256 let user = User::dummy_admin();
257 let resource = Faker.fake::<MockedResource>();
258
259 let res = authorization_service.can_on_instance(&Some(user), &Action::Read, &resource);
261
262 assert!(res.is_ok())
264 }
265
266 #[test]
267 fn should_return_ok_if_the_user_shares_a_group_with_the_resource() {
268 let authorization_service = SimpleAuthorizationEngine;
270
271 let shared_group = Faker.fake::<Group>();
272 let user = User {
273 groups: [Faker.fake(), shared_group.clone()].into(),
274 ..Faker.fake()
275 };
276 let resource = MockedResource {
277 groups: Some(vec![shared_group]),
278 user_emails: None,
279 ..Faker.fake()
280 };
281
282 let res = authorization_service.can_on_instance(&Some(user), &Action::Read, &resource);
284
285 assert!(res.is_ok())
287 }
288
289 #[test]
290 fn should_return_err_if_the_user_does_not_share_a_group_with_the_resource() {
291 let authorization_service = SimpleAuthorizationEngine;
293
294 let user = Faker.fake::<User>();
295 let resource = MockedResource {
296 groups: Some(vec![]),
297 user_emails: None,
298 ..Faker.fake()
299 };
300
301 let res = authorization_service.can_on_instance(&Some(user), &Action::Read, &resource);
303
304 assert!(matches!(
306 res,
307 Err(AuthorizationEngineError::NotAuthorized { .. })
308 ))
309 }
310
311 #[test]
312 fn should_return_err_if_the_user_is_not_authenticated() {
313 let authorization_service = SimpleAuthorizationEngine;
315
316 let res = authorization_service.can_on_instance(
318 &None,
319 &Action::Read,
320 &Faker.fake::<MockedResource>(),
321 );
322
323 assert!(matches!(
325 res,
326 Err(AuthorizationEngineError::NotAuthenticated)
327 ))
328 }
329
330 #[test_case::test_case(&Action::List; "list")]
331 #[test_case::test_case(&Action::Create; "create")]
332 #[test_case::test_case(&Action::Update; "update")]
333 #[test_case::test_case(&Action::Delete; "delete")]
334 fn should_return_err_if_anything_other_than_read(action: &Action) {
335 let authorization_service = SimpleAuthorizationEngine;
337
338 let shared_group = Faker.fake::<Group>();
339 let user = User {
340 groups: [Faker.fake(), shared_group.clone()].into(),
341 ..Faker.fake()
342 };
343 let resource = MockedResource {
344 groups: Some(vec![shared_group]),
345 user_emails: None,
346 ..Faker.fake()
347 };
348
349 let res = authorization_service.can_on_instance(&Some(user), action, &resource);
351
352 assert!(matches!(
354 res,
355 Err(AuthorizationEngineError::NotAuthorized { .. })
356 ))
357 }
358 }
359}