1+ use authz:: RollingStockPrivilege ;
2+ use authz:: v2:: rolling_stock_privileges;
3+ use axum:: Extension ;
14use axum:: extract:: Json ;
25use axum:: extract:: Path ;
36use axum:: extract:: Query ;
@@ -28,6 +31,7 @@ use super::RollingStockError;
2831use super :: RollingStockIdParam ;
2932use super :: RollingStockKey ;
3033use super :: RollingStockNameParam ;
34+ use crate :: AppState ;
3135use crate :: error:: Result ;
3236use crate :: views:: pagination:: PaginatedList ;
3337use crate :: views:: pagination:: PaginationQueryParams ;
@@ -118,9 +122,22 @@ pub(in crate::views) async fn list(
118122 )
119123) ]
120124pub ( in crate :: views) async fn get (
121- State ( db_pool) : State < Arc < DbConnectionPoolV2 > > ,
125+ State ( AppState {
126+ regulator, db_pool, ..
127+ } ) : State < AppState > ,
128+ Extension ( authn_state) : Extension < crate :: authentication:: State > ,
122129 Path ( light_rolling_stock_id) : Path < i64 > ,
123130) -> Result < Json < LightRollingStockWithLiveries > > {
131+ if let Some ( user) = authn_state. regular_user ( ) {
132+ let authorizer = authn_state. authorizer ( regulator. openfga ( ) , db_pool. get ( ) . await ?) ;
133+ crate :: authorizers:: require (
134+ & authorizer,
135+ rolling_stock_privileges ( user, authz:: RollingStock ( light_rolling_stock_id) ) ,
136+ & RollingStockPrivilege :: CanRead ,
137+ )
138+ . await ?;
139+ }
140+
124141 let rolling_stock =
125142 RollingStock :: retrieve_or_fail ( db_pool. get ( ) . await ?, light_rolling_stock_id, || {
126143 RollingStockError :: KeyNotFound {
@@ -144,7 +161,10 @@ pub(in crate::views) async fn get(
144161 )
145162) ]
146163pub ( in crate :: views) async fn get_by_name (
147- State ( db_pool) : State < Arc < DbConnectionPoolV2 > > ,
164+ State ( AppState {
165+ regulator, db_pool, ..
166+ } ) : State < AppState > ,
167+ Extension ( authn_state) : Extension < crate :: authentication:: State > ,
148168 Path ( light_rolling_stock_name) : Path < String > ,
149169) -> Result < Json < LightRollingStockWithLiveries > > {
150170 let rolling_stock = RollingStock :: retrieve_or_fail (
@@ -155,6 +175,17 @@ pub(in crate::views) async fn get_by_name(
155175 } ,
156176 )
157177 . await ?;
178+
179+ if let Some ( user) = authn_state. regular_user ( ) {
180+ let authorizer = authn_state. authorizer ( regulator. openfga ( ) , db_pool. get ( ) . await ?) ;
181+ crate :: authorizers:: require (
182+ & authorizer,
183+ rolling_stock_privileges ( user, authz:: RollingStock ( rolling_stock. id ) ) ,
184+ & RollingStockPrivilege :: CanRead ,
185+ )
186+ . await ?;
187+ }
188+
158189 let light_rolling_stock_with_liveries =
159190 LightRollingStockWithLiveries :: try_fetch ( & mut db_pool. get ( ) . await ?, rolling_stock) . await ?;
160191 Ok ( Json ( light_rolling_stock_with_liveries) )
@@ -281,6 +312,7 @@ mod tests {
281312 use crate :: error:: InternalError ;
282313 use crate :: fixtures:: create_fast_rolling_stock;
283314 use crate :: views:: test_app;
315+ use crate :: views:: test_app:: TestRequestExt ;
284316
285317 fn is_sorted ( data : & [ i64 ] ) -> bool {
286318 for elem in data. windows ( 2 ) {
@@ -300,15 +332,23 @@ mod tests {
300332 #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
301333 async fn get_light_rolling_stock ( ) {
302334 // GIVEN
303- let app = test_app ! ( ) . skip_authz ( ) . build ( ) ;
335+ let app = test_app ! ( ) . build ( ) ;
304336 let db_pool = app. db_pool ( ) ;
305337
306338 let rs_name = "fast_rolling_stock_name" ;
307339 let fast_rolling_stock = create_fast_rolling_stock ( & mut db_pool. get_ok ( ) , rs_name) . await ;
308340
341+ // a user with a read grant on the rolling stock
342+ let user = app
343+ . user ( "authorized" , "Authorized" )
344+ . with_rolling_stock_grant ( fast_rolling_stock. id , authz:: RollingStockGrant :: Reader )
345+ . create ( )
346+ . await ;
347+
309348 // WHEN
310349 let response: LightRollingStockWithLiveries = app
311350 . get ( format ! ( "/light_rolling_stock/{}" , fast_rolling_stock. id) . as_str ( ) )
351+ . by_user ( & user. info )
312352 . await
313353 . assert_status_ok ( )
314354 . json ( ) ;
@@ -320,15 +360,23 @@ mod tests {
320360 #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
321361 async fn get_light_rolling_stock_by_name ( ) {
322362 // GIVEN
323- let app = test_app ! ( ) . skip_authz ( ) . build ( ) ;
363+ let app = test_app ! ( ) . build ( ) ;
324364 let db_pool = app. db_pool ( ) ;
325365
326366 let rs_name = "fast_rolling_stock_name" ;
327367 let fast_rolling_stock = create_fast_rolling_stock ( & mut db_pool. get_ok ( ) , rs_name) . await ;
328368
369+ // a user with a read grant on the rolling stock
370+ let user = app
371+ . user ( "authorized" , "Authorized" )
372+ . with_rolling_stock_grant ( fast_rolling_stock. id , authz:: RollingStockGrant :: Reader )
373+ . create ( )
374+ . await ;
375+
329376 // WHEN
330377 let response: LightRollingStockWithLiveries = app
331378 . get ( format ! ( "/light_rolling_stock/name/{rs_name}" ) . as_str ( ) )
379+ . by_user ( & user. info )
332380 . await
333381 . assert_status_ok ( )
334382 . json ( ) ;
@@ -347,6 +395,48 @@ mod tests {
347395 . assert_status_not_found ( ) ;
348396 }
349397
398+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
399+ async fn get_light_rolling_stock_without_permission ( ) {
400+ let app = test_app ! ( ) . build ( ) ;
401+ let db_pool = app. db_pool ( ) ;
402+
403+ let fast_rolling_stock =
404+ create_fast_rolling_stock ( & mut db_pool. get_ok ( ) , "fast_rolling_stock_name" ) . await ;
405+
406+ // a user that has the role to reach the endpoint but no read grant on the rolling stock
407+ let user = app
408+ . user ( "unauthorized" , "Unauthorized" )
409+ . with_roles ( [ authz:: Role :: OperationalStudies ] )
410+ . create ( )
411+ . await ;
412+
413+ app. get ( format ! ( "/light_rolling_stock/{}" , fast_rolling_stock. id) . as_str ( ) )
414+ . by_user ( & user. info )
415+ . await
416+ . assert_status_forbidden ( ) ;
417+ }
418+
419+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
420+ async fn get_light_rolling_stock_by_name_without_permission ( ) {
421+ let app = test_app ! ( ) . build ( ) ;
422+ let db_pool = app. db_pool ( ) ;
423+
424+ let rs_name = "fast_rolling_stock_name" ;
425+ create_fast_rolling_stock ( & mut db_pool. get_ok ( ) , rs_name) . await ;
426+
427+ // a user that has the role to reach the endpoint but no read grant on the rolling stock
428+ let user = app
429+ . user ( "unauthorized" , "Unauthorized" )
430+ . with_roles ( [ authz:: Role :: OperationalStudies ] )
431+ . create ( )
432+ . await ;
433+
434+ app. get ( format ! ( "/light_rolling_stock/name/{rs_name}" ) . as_str ( ) )
435+ . by_user ( & user. info )
436+ . await
437+ . assert_status_forbidden ( ) ;
438+ }
439+
350440 #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
351441 async fn list_light_rolling_stock_increasing_ids ( ) {
352442 let app = test_app ! ( ) . skip_authz ( ) . build ( ) ;
0 commit comments