11use authz:: RollingStockPrivilege ;
2+ use authz:: v2:: Authorizer ;
3+ use authz:: v2:: Check ;
24use authz:: v2:: rolling_stock_privileges;
35use axum:: Extension ;
46use axum:: extract:: Json ;
@@ -8,7 +10,6 @@ use axum::extract::State;
810use common:: units;
911use common:: units:: quantities:: Length ;
1012use database:: DbConnection ;
11- use database:: DbConnectionPoolV2 ;
1213use editoast_models:: prelude:: * ;
1314use editoast_models:: rolling_stock:: RollingStock ;
1415use editoast_models:: rolling_stock:: TrainMainCategory ;
@@ -22,7 +23,6 @@ use schemas::rolling_stock::SupportedSignalingSystem;
2223use serde:: Serialize ;
2324use std:: collections:: HashMap ;
2425use std:: collections:: HashSet ;
25- use std:: sync:: Arc ;
2626use uom:: si:: f64:: Mass ;
2727use uom:: si:: f64:: Velocity ;
2828use utoipa:: ToSchema ;
@@ -32,7 +32,9 @@ use super::RollingStockIdParam;
3232use super :: RollingStockKey ;
3333use super :: RollingStockNameParam ;
3434use crate :: AppState ;
35+ use crate :: authorizers:: impossible;
3536use crate :: error:: Result ;
37+ use crate :: views:: AuthorizationError ;
3638use crate :: views:: pagination:: PaginatedList ;
3739use crate :: views:: pagination:: PaginationQueryParams ;
3840use crate :: views:: pagination:: PaginationStats ;
@@ -88,14 +90,48 @@ pub(in crate::views) struct LightRollingStockWithLiveriesCountList {
8890 )
8991) ]
9092pub ( in crate :: views) async fn list (
91- State ( db_pool) : State < Arc < DbConnectionPoolV2 > > ,
93+ State ( AppState {
94+ db_pool, regulator, ..
95+ } ) : State < AppState > ,
96+
97+ Extension ( authn_state) : Extension < crate :: authentication:: State > ,
9298 Query ( page_settings) : Query < PaginationQueryParams < 1000 > > ,
9399) -> Result < Json < LightRollingStockWithLiveriesCountList > > {
94- let settings = page_settings
95- . into_selection_settings ( )
96- . order_by ( || RollingStock :: ID . asc ( ) ) ;
100+ let conn = & mut db_pool. get ( ) . await ?;
101+ let default_settings = page_settings. into_selection_settings ( ) ;
102+ let settings = if let Some ( user) = authn_state. regular_user ( ) {
103+ let authorizer = authn_state. authorizer ( regulator. openfga ( ) , db_pool. get ( ) . await ?) ;
104+ let authorized_rolling_stocks = authorizer
105+ . authorize ( authz:: v2:: rolling_stock_list (
106+ user,
107+ RollingStockPrivilege :: CanRead ,
108+ ) )
109+ . await ?
110+ . access ( )
111+ . await ?
112+ . map_err ( |err| match err {
113+ Check :: SubjectExists ( _) => AuthorizationError :: Forbidden ,
114+ check => impossible ! ( check) ,
115+ } ) ?;
116+ match authorized_rolling_stocks {
117+ authz:: v2:: ResourcesList :: All => default_settings,
118+ authz:: v2:: ResourcesList :: Privileged ( authorized_rolling_stocks) => default_settings
119+ . filter ( move || {
120+ RollingStock :: ID . eq_any (
121+ authorized_rolling_stocks
122+ . iter ( )
123+ . map ( |rolling_stock| rolling_stock. 0 )
124+ . collect ( ) ,
125+ )
126+ } ) ,
127+ }
128+ } else {
129+ default_settings
130+ } ;
131+
97132 let ( rolling_stocks, stats) =
98- RollingStock :: list_paginated ( & mut db_pool. get ( ) . await ?, settings) . await ?;
133+ RollingStock :: list_paginated ( conn, settings. order_by ( move || RollingStock :: ID . asc ( ) ) )
134+ . await ?;
99135
100136 let results = rolling_stocks. into_iter ( ) . zip ( db_pool. iter_conn ( ) ) . map (
101137 |( rolling_stock, conn) | async move {
@@ -302,6 +338,8 @@ impl From<ModeEffortCurves> for LightModeEffortCurves {
302338
303339#[ cfg( test) ]
304340mod tests {
341+ use authz:: Role ;
342+ use authz:: RollingStockGrant ;
305343 use pretty_assertions:: assert_eq;
306344 use std:: collections:: HashSet ;
307345
@@ -329,6 +367,57 @@ mod tests {
329367 app. get ( "/light_rolling_stock" ) . await . assert_status_ok ( ) ;
330368 }
331369
370+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
371+ async fn rolling_stock_list_filters_authorized_rolling_stocks ( ) {
372+ let app = test_app ! ( ) . build ( ) ;
373+ let db_pool = app. db_pool ( ) ;
374+ let rolling_stock_grant =
375+ create_fast_rolling_stock ( & mut db_pool. get_ok ( ) , "fast_rolling_stock_grant" ) . await ;
376+ let rolling_stock_no_grant =
377+ create_fast_rolling_stock ( & mut db_pool. get_ok ( ) , "fast_rolling_stock_no_grant" ) . await ;
378+ // Regular user with the correct roles should see only the rolling stock he is associated with:
379+ let user = app
380+ . user ( "user_identity" , "user_name" )
381+ . with_rolling_stock_grant ( rolling_stock_grant. id , RollingStockGrant :: Reader )
382+ . create ( )
383+ . await ;
384+ let response: LightRollingStockWithLiveriesCountList = app
385+ . get ( "/light_rolling_stock" )
386+ . by_user ( user. as_ref ( ) )
387+ . await
388+ . assert_status_ok ( )
389+ . json ( ) ;
390+ assert_eq ! (
391+ response
392+ . results
393+ . iter( )
394+ . map( |rolling_stock| rolling_stock. rolling_stock. id)
395+ . collect:: <Vec <_>>( ) ,
396+ vec![ rolling_stock_grant. id]
397+ ) ;
398+ // TODO: separate in two tests (admins_can_list_all_rolling_stocks and user_can_list_their_rolling_stocks)
399+ // An admin should see all the rolling stocks:
400+ let admin = app
401+ . user ( "admin" , "admin" )
402+ . with_roles ( [ Role :: Admin ] )
403+ . create ( )
404+ . await ;
405+ let response: LightRollingStockWithLiveriesCountList = app
406+ . get ( "/light_rolling_stock/" )
407+ . by_user ( admin. as_ref ( ) )
408+ . await
409+ . assert_status_ok ( )
410+ . json ( ) ;
411+ assert_eq ! (
412+ response
413+ . results
414+ . iter( )
415+ . map( |rolling_stock| rolling_stock. rolling_stock. id)
416+ . collect:: <Vec <_>>( ) ,
417+ vec![ rolling_stock_grant. id, rolling_stock_no_grant. id]
418+ ) ;
419+ }
420+
332421 #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
333422 async fn get_light_rolling_stock ( ) {
334423 // GIVEN
0 commit comments