@@ -42,7 +42,6 @@ use utoipa::ToSchema;
4242use crate :: AppState ;
4343use crate :: error:: InternalError ;
4444use crate :: error:: Result ;
45- use crate :: views:: AuthorizationError ;
4645
4746#[ derive( Debug , Serialize , ToSchema ) ]
4847pub struct RollingStockWithLiveries {
@@ -322,7 +321,7 @@ pub(in crate::views) async fn update(
322321 State ( AppState {
323322 regulator, db_pool, ..
324323 } ) : State < AppState > ,
325- Extension ( state ) : Extension < crate :: authentication:: State > ,
324+ Extension ( authn_state ) : Extension < crate :: authentication:: State > ,
326325 Path ( rolling_stock_id) : Path < i64 > ,
327326 Json ( rolling_stock_form) : Json < RollingStockForm > ,
328327) -> Result < Json < RollingStockWithLiveries > > {
@@ -333,10 +332,8 @@ pub(in crate::views) async fn update(
333332 rolling_stock_privileges ( user, authz:: RollingStock ( rolling_stock_id) ) ,
334333 & RollingStockPrivilege :: CanWrite ,
335334 )
336- . await ?
337- {
338- return Err ( AuthorizationError :: Forbidden . into ( ) ) ;
339- } ;
335+ . await ?;
336+ }
340337 let new_rolling_stock = db_pool
341338 . get ( )
342339 . await ?
@@ -396,10 +393,23 @@ pub(in crate::views) struct DeleteRollingStockQueryParams {
396393 )
397394) ]
398395pub ( in crate :: views) async fn delete (
399- State ( db_pool) : State < Arc < DbConnectionPoolV2 > > ,
396+ State ( AppState {
397+ regulator, db_pool, ..
398+ } ) : State < AppState > ,
399+ Extension ( authn_state) : Extension < crate :: authentication:: State > ,
400400 Path ( rolling_stock_id) : Path < i64 > ,
401401 Query ( DeleteRollingStockQueryParams { force } ) : Query < DeleteRollingStockQueryParams > ,
402402) -> Result < impl IntoResponse > {
403+ if let Some ( user) = authn_state. regular_user ( ) {
404+ let authorizer = authn_state. authorizer ( regulator. openfga ( ) , db_pool. get ( ) . await ?) ;
405+ crate :: authorizers:: require (
406+ & authorizer,
407+ rolling_stock_privileges ( user, authz:: RollingStock ( rolling_stock_id) ) ,
408+ & RollingStockPrivilege :: CanDelete ,
409+ )
410+ . await ?;
411+ }
412+
403413 let conn = & mut db_pool. get ( ) . await ?;
404414
405415 let rolling_stock = RollingStock :: retrieve_or_fail ( conn. clone ( ) , rolling_stock_id, || {
@@ -1520,7 +1530,7 @@ pub mod tests {
15201530 #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
15211531 async fn delete_locked_rolling_stock_fails ( ) {
15221532 // GIVEN
1523- let app = test_app ! ( ) . skip_authz ( ) . build ( ) ;
1533+ let app = test_app ! ( ) . build ( ) ;
15241534 let db_pool = app. db_pool ( ) ;
15251535
15261536 let locked_rs_name = "locked_fast_rolling_stock_name" ;
@@ -1534,9 +1544,16 @@ pub mod tests {
15341544 . await
15351545 . expect ( "Failed to create rolling stock" ) ;
15361546
1547+ let user = app
1548+ . user ( "owner" , "Owner" )
1549+ . with_roles ( [ Role :: OperationalStudies ] )
1550+ . with_rolling_stock_grant ( locked_fast_rolling_stock. id , RollingStockGrant :: Owner )
1551+ . create ( )
1552+ . await ;
15371553 // WHEN
15381554 let raw_response = app
15391555 . delete ( format ! ( "/rolling_stock/{}" , locked_fast_rolling_stock. id) . as_str ( ) )
1556+ . by_user ( & user. info )
15401557 . await ;
15411558
15421559 // THEN
@@ -1555,16 +1572,24 @@ pub mod tests {
15551572 #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
15561573 async fn delete_unlocked_unused_rolling_stock_succeeds ( ) {
15571574 // GIVEN
1558- let app = test_app ! ( ) . skip_authz ( ) . build ( ) ;
1575+ let app = test_app ! ( ) . build ( ) ;
15591576 let db_pool = app. db_pool ( ) ;
15601577
15611578 let rs_name = "fast_rolling_stock_name" ;
15621579 let fast_rolling_stock = create_fast_rolling_stock ( & mut db_pool. get_ok ( ) , rs_name) . await ;
15631580 assert ! ( !fast_rolling_stock. locked) ;
15641581
1582+ let user = app
1583+ . user ( "owner" , "Owner" )
1584+ . with_roles ( [ Role :: OperationalStudies ] )
1585+ . with_rolling_stock_grant ( fast_rolling_stock. id , RollingStockGrant :: Owner )
1586+ . create ( )
1587+ . await ;
1588+
15651589 // WHEN
15661590 let raw_response = app
15671591 . delete ( format ! ( "/rolling_stock/{}" , fast_rolling_stock. id) . as_str ( ) )
1592+ . by_user ( & user. info )
15681593 . await ;
15691594
15701595 // THEN
@@ -1577,6 +1602,41 @@ pub mod tests {
15771602 assert ! ( !rolling_stock_exists) ;
15781603 }
15791604
1605+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
1606+ async fn delete_rolling_stock_without_authorization ( ) {
1607+ // GIVEN
1608+ let app = test_app ! ( ) . build ( ) ;
1609+ let db_pool = app. db_pool ( ) ;
1610+
1611+ let fast_rolling_stock =
1612+ create_fast_rolling_stock ( & mut db_pool. get_ok ( ) , "unauthorized_rolling_stock" ) . await ;
1613+
1614+ // A user with the OperationalStudies role but only a write grant on the rolling stock
1615+ // (delete requires an owner/delete grant)
1616+ let user = app
1617+ . user ( "writer" , "Writer" )
1618+ . with_roles ( [ Role :: OperationalStudies ] )
1619+ . with_rolling_stock_grant ( fast_rolling_stock. id , RollingStockGrant :: Writer )
1620+ . create ( )
1621+ . await ;
1622+
1623+ // WHEN
1624+ let raw_response = app
1625+ . delete ( format ! ( "/rolling_stock/{}" , fast_rolling_stock. id) . as_str ( ) )
1626+ . by_user ( user. as_ref ( ) )
1627+ . await ;
1628+
1629+ // THEN
1630+ raw_response. assert_status_forbidden ( ) ;
1631+
1632+ // The rolling stock should still exist
1633+ let rolling_stock_exists =
1634+ RollingStock :: exists ( & mut db_pool. get_ok ( ) , fast_rolling_stock. id )
1635+ . await
1636+ . expect ( "Failed to check if rolling stock exists" ) ;
1637+ assert ! ( rolling_stock_exists) ;
1638+ }
1639+
15801640 #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
15811641 async fn delete_unlocked_used_rolling_stock_requires_force_flag ( ) {
15821642 // GIVEN
0 commit comments