potree_auth/render/application/
service.rs1use std::sync::Arc;
2
3use web_route::ParameterizedRoute;
4use web_route::WebRoute;
5
6use super::super::domain::authorization::PotreeRenderResource;
7use super::super::domain::authorization::ProjectDashboardResource;
8use super::super::domain::not_found_render::NotFound;
9use super::super::domain::potree_render::PotreeRender;
10use super::super::domain::project_dashboard_render::ProjectDashboard;
11use super::error::RenderingServiceError;
12use crate::authorization::domain::action::Action;
13use crate::authorization::ports::authorization_engine::AuthorizationEngine;
14use crate::project::domain::Project;
15use crate::project::domain::ProjectId;
16use crate::project::domain::authorization::ProjectTypeResource;
17use crate::project::ports::project_repository::ProjectRepository;
18use crate::user::domain::User;
19
20#[derive(Debug, Clone)]
22pub struct RenderingService {
23 project_repository: Arc<dyn ProjectRepository>,
25
26 authorization_engine: Arc<dyn AuthorizationEngine>,
28
29 project_assets_route: ParameterizedRoute,
31
32 potree_assets_route: WebRoute,
34}
35
36impl RenderingService {
37 pub fn new(
38 project_datastore: Arc<dyn ProjectRepository>,
39 authorization_engine: Arc<dyn AuthorizationEngine>,
40 project_assets_route: ParameterizedRoute,
41 potree_assets_route: WebRoute,
42 ) -> Self {
43 Self {
44 project_repository: project_datastore,
45 authorization_engine,
46 project_assets_route,
47 potree_assets_route,
48 }
49 }
50
51 pub async fn render_potree(
58 &self,
59 user: &Option<User>,
60 project_id: &ProjectId,
61 ) -> Result<PotreeRender, RenderingServiceError> {
62 let project = self.project_repository.read(project_id).await?;
63
64 let potree_render_resource = PotreeRenderResource {
65 associated_project: &project,
66 };
67 self.authorization_engine
68 .can_on_instance(user, &Action::Read, &potree_render_resource)?;
69
70 Ok(PotreeRender {
71 project_title: project.name,
72 potree_static_assets_path: self.potree_assets_route.clone(),
73 potree_config_path: self.potree_config_route(project_id)?,
74 })
75 }
76
77 pub async fn project_dashboard(
79 &self,
80 user: &Option<User>,
81 default_project_render_route: &ParameterizedRoute,
82 ) -> Result<ProjectDashboard, RenderingServiceError> {
83 self.authorization_engine
84 .can_on_type(user, &Action::Read, &ProjectDashboardResource)?;
85
86 let projects = self.list_allowed_projects(user).await?;
87
88 Ok(ProjectDashboard::from_domain_projects(
89 projects,
90 default_project_render_route,
91 )?)
92 }
93
94 pub async fn not_found(&self) -> Result<NotFound, RenderingServiceError> {
96 Ok(NotFound)
97 }
98
99 fn potree_config_route(
102 &self,
103 project_id: &ProjectId,
104 ) -> Result<WebRoute, RenderingServiceError> {
105 let params = serde_json::json!({
112 "project_id": project_id,
113 "path": "potree.json5",
114 });
115
116 Ok(self.project_assets_route.to_web_route(¶ms)?)
117 }
118
119 async fn list_allowed_projects(
121 &self,
122 user: &Option<User>,
123 ) -> Result<Vec<Project>, RenderingServiceError> {
124 self.authorization_engine
125 .can_on_type(user, &Action::List, &ProjectTypeResource)?;
126
127 let projects = self.project_repository.list().await?;
128
129 let allowed_projects = projects
131 .into_iter()
132 .filter(|p| {
133 self.authorization_engine
134 .can_on_instance(user, &Action::Read, p)
135 .is_ok()
136 })
137 .collect();
138
139 Ok(allowed_projects)
140 }
141}
142
143#[cfg(test)]
144mod project_rendering_service_tests {
145 use std::sync::Arc;
146
147 use fake::Fake;
148 use fake::Faker;
149 use web_route::ParameterizedRoute;
150 use web_route::WebRoute;
151
152 use super::super::super::application::error::RenderingServiceError;
153 use super::super::super::application::service::RenderingService;
154 use crate::authorization::domain::action::Action;
155 use crate::authorization::domain::error::AuthorizationEngineError;
156 use crate::authorization::ports::authorization_engine::MockAuthorizationEngine;
157 use crate::project::ports::project_repository::MockProjectRepository;
158
159 mod render_potree {
160
161 use super::*;
162
163 #[tokio::test]
164 async fn should_return_the_correct_error_if_user_not_authenticated() {
165 let mut project_datastore = MockProjectRepository::new();
167 project_datastore
168 .expect_read()
169 .return_const(Ok(Faker.fake()));
170 let mut authorization_engine = MockAuthorizationEngine::new();
171 authorization_engine
172 .expect_can_on_instance()
173 .return_const(Err(AuthorizationEngineError::NotAuthenticated));
174
175 let rendering_service = RenderingService::new(
176 Arc::new(project_datastore),
177 Arc::new(authorization_engine),
178 ParameterizedRoute::new(Faker.fake::<WebRoute>()).join("/{project_id}/{*path}"),
179 Faker.fake(),
180 );
181
182 let res = rendering_service
184 .render_potree(&Faker.fake(), &Faker.fake())
185 .await;
186
187 assert!(matches!(res, Err(RenderingServiceError::NotAuthenticated)));
189 }
190
191 #[tokio::test]
192 async fn should_return_the_correct_error_if_user_not_authorized() {
193 let mut project_datastore = MockProjectRepository::new();
195 project_datastore
196 .expect_read()
197 .return_const(Ok(Faker.fake()));
198 let mut authorization_engine = MockAuthorizationEngine::new();
199 authorization_engine
200 .expect_can_on_instance()
201 .return_const(Err(AuthorizationEngineError::NotAuthorized {
202 user: Faker.fake(),
203 action: Action::Read,
204 resource_identifier: Faker.fake(),
205 resource_type: Faker.fake(),
206 }));
207
208 let rendering_service = RenderingService::new(
209 Arc::new(project_datastore),
210 Arc::new(authorization_engine),
211 ParameterizedRoute::new(Faker.fake::<WebRoute>()).join("/{project_id}/{*path}"),
212 Faker.fake(),
213 );
214
215 let res = rendering_service
217 .render_potree(&Faker.fake(), &Faker.fake())
218 .await;
219
220 assert!(matches!(
222 res,
223 Err(RenderingServiceError::NotAuthorized { .. })
224 ));
225 }
226 }
227
228 mod list_allowed_projects {
229 use std::sync::Mutex;
230
231 use super::*;
232 use crate::project::domain::Project;
233
234 #[tokio::test]
235 async fn should_return_the_projects_that_the_user_is_allowed_to_read() {
236 let dummy_projects = Faker.fake::<Vec<Project>>();
238
239 let mut project_datastore = MockProjectRepository::new();
240 project_datastore
241 .expect_list()
242 .return_const(Ok(dummy_projects.clone()));
243 let mut authorization_engine = MockAuthorizationEngine::new();
244 authorization_engine
247 .expect_can_on_type()
248 .once()
249 .return_const(Ok(()));
250 let authorization_engine_call_count = Arc::new(Mutex::new(0));
252 let authorization_engine_call_count_clone =
253 Arc::clone(&authorization_engine_call_count);
254 authorization_engine
255 .expect_can_on_instance()
256 .returning(move |_, _, _| {
257 let mut count = authorization_engine_call_count_clone.lock().unwrap();
258 let res = if *count % 2 == 0 {
259 Err(AuthorizationEngineError::NotAuthorized {
260 user: Faker.fake(),
261 action: Action::List,
262 resource_identifier: Some(Faker.fake()),
263 resource_type: Faker.fake(),
264 })
265 } else {
266 Ok(())
267 };
268 *count += 1;
269 res
270 });
271
272 let rendering_service = RenderingService::new(
273 Arc::new(project_datastore),
274 Arc::new(authorization_engine),
275 ParameterizedRoute::new(Faker.fake::<WebRoute>()).join("/{project_id}/{*path}"),
276 Faker.fake(),
277 );
278
279 let allowed_projects = rendering_service
281 .list_allowed_projects(&Faker.fake())
282 .await
283 .unwrap();
284
285 assert_eq!(allowed_projects.len(), dummy_projects.len() / 2);
287 }
288
289 #[tokio::test]
290 async fn should_return_the_correct_error_if_user_not_allowed_to_list_projects() {
291 let project_datastore = MockProjectRepository::new();
293 let mut authorization_engine = MockAuthorizationEngine::new();
294 authorization_engine
295 .expect_can_on_type()
296 .once()
297 .return_const(Err(AuthorizationEngineError::NotAuthorized {
298 user: Faker.fake(),
299 action: Action::List,
300 resource_identifier: None,
301 resource_type: Faker.fake(),
302 }));
303
304 let rendering_service = RenderingService::new(
305 Arc::new(project_datastore),
306 Arc::new(authorization_engine),
307 ParameterizedRoute::new(Faker.fake::<WebRoute>()).join("/{project_id}/{*path}"),
308 Faker.fake(),
309 );
310
311 let res = rendering_service.list_allowed_projects(&Faker.fake()).await;
313
314 assert!(matches!(
316 res,
317 Err(RenderingServiceError::NotAuthorized { .. })
318 ));
319 }
320 }
321}