Skip to content

Commit 15da9ff

Browse files
committed
editoast: add check privilege to delete rolling stock endpoint
Signed-off-by: Youness CHRIFI ALAOUI <youness.chrifi@gmail.com>
1 parent bbd653b commit 15da9ff

1 file changed

Lines changed: 149 additions & 3 deletions

File tree

editoast/src/views/rolling_stock.rs

Lines changed: 149 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -394,10 +394,23 @@ pub(in crate::views) struct DeleteRollingStockQueryParams {
394394
)
395395
)]
396396
pub(in crate::views) async fn delete(
397-
State(db_pool): State<Arc<DbConnectionPoolV2>>,
397+
State(AppState {
398+
regulator, db_pool, ..
399+
}): State<AppState>,
400+
Extension(authn_state): Extension<crate::authentication::State>,
398401
Path(rolling_stock_id): Path<i64>,
399402
Query(DeleteRollingStockQueryParams { force }): Query<DeleteRollingStockQueryParams>,
400403
) -> Result<impl IntoResponse> {
404+
if let Some(user) = authn_state.regular_user() {
405+
let authorizer = authn_state.authorizer(regulator.openfga(), db_pool.get().await?);
406+
crate::authorizers::require(
407+
&authorizer,
408+
rolling_stock_privileges(user, authz::RollingStock(rolling_stock_id)),
409+
&RollingStockPrivilege::CanDelete,
410+
)
411+
.await?;
412+
}
413+
401414
let conn = &mut db_pool.get().await?;
402415

403416
let rolling_stock = RollingStock::retrieve_or_fail(conn.clone(), rolling_stock_id, || {
@@ -1619,7 +1632,7 @@ pub mod tests {
16191632
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
16201633
async fn delete_locked_rolling_stock_fails() {
16211634
// GIVEN
1622-
let app = test_app!().skip_authz().build();
1635+
let app = test_app!().build();
16231636
let db_pool = app.db_pool();
16241637

16251638
let locked_rs_name = "locked_fast_rolling_stock_name";
@@ -1633,9 +1646,16 @@ pub mod tests {
16331646
.await
16341647
.expect("Failed to create rolling stock");
16351648

1649+
let user = app
1650+
.user("owner", "Owner")
1651+
.with_roles([Role::OperationalStudies])
1652+
.with_rolling_stock_grant(locked_fast_rolling_stock.id, RollingStockGrant::Owner)
1653+
.create()
1654+
.await;
16361655
// WHEN
16371656
let raw_response = app
16381657
.delete(format!("/rolling_stock/{}", locked_fast_rolling_stock.id).as_str())
1658+
.by_user(&user.info)
16391659
.await;
16401660

16411661
// THEN
@@ -1654,16 +1674,24 @@ pub mod tests {
16541674
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
16551675
async fn delete_unlocked_unused_rolling_stock_succeeds() {
16561676
// GIVEN
1657-
let app = test_app!().skip_authz().build();
1677+
let app = test_app!().build();
16581678
let db_pool = app.db_pool();
16591679

16601680
let rs_name = "fast_rolling_stock_name";
16611681
let fast_rolling_stock = create_fast_rolling_stock(&mut db_pool.get_ok(), rs_name).await;
16621682
assert!(!fast_rolling_stock.locked);
16631683

1684+
let user = app
1685+
.user("owner", "Owner")
1686+
.with_roles([Role::OperationalStudies])
1687+
.with_rolling_stock_grant(fast_rolling_stock.id, RollingStockGrant::Owner)
1688+
.create()
1689+
.await;
1690+
16641691
// WHEN
16651692
let raw_response = app
16661693
.delete(format!("/rolling_stock/{}", fast_rolling_stock.id).as_str())
1694+
.by_user(&user.info)
16671695
.await;
16681696

16691697
// THEN
@@ -1676,6 +1704,124 @@ pub mod tests {
16761704
assert!(!rolling_stock_exists);
16771705
}
16781706

1707+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1708+
async fn delete_rolling_stock_without_authorization() {
1709+
// GIVEN
1710+
let app = test_app!().build();
1711+
let db_pool = app.db_pool();
1712+
1713+
let fast_rolling_stock =
1714+
create_fast_rolling_stock(&mut db_pool.get_ok(), "unauthorized_rolling_stock").await;
1715+
1716+
// A user with the OperationalStudies role but only a write grant on the rolling stock
1717+
// (delete requires an owner/delete grant)
1718+
let user = app
1719+
.user("writer", "Writer")
1720+
.with_roles([Role::OperationalStudies])
1721+
.with_rolling_stock_grant(fast_rolling_stock.id, RollingStockGrant::Writer)
1722+
.create()
1723+
.await;
1724+
1725+
// WHEN
1726+
let raw_response = app
1727+
.delete(format!("/rolling_stock/{}", fast_rolling_stock.id).as_str())
1728+
.by_user(user.as_ref())
1729+
.await;
1730+
1731+
// THEN
1732+
raw_response.assert_status_forbidden();
1733+
1734+
// The rolling stock should still exist
1735+
let rolling_stock_exists =
1736+
RollingStock::exists(&mut db_pool.get_ok(), fast_rolling_stock.id)
1737+
.await
1738+
.expect("Failed to check if rolling stock exists");
1739+
assert!(rolling_stock_exists);
1740+
}
1741+
1742+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1743+
async fn delete_rolling_stock_as_admin_without_grant() {
1744+
// GIVEN
1745+
let app = test_app!().build();
1746+
let db_pool = app.db_pool();
1747+
1748+
let fast_rolling_stock =
1749+
create_fast_rolling_stock(&mut db_pool.get_ok(), "admin_deleted_rolling_stock").await;
1750+
1751+
// An admin holds no grant on the rolling stock but can still delete it
1752+
let admin = app
1753+
.user("admin", "Admin")
1754+
.with_roles([Role::Admin])
1755+
.create()
1756+
.await;
1757+
1758+
// WHEN
1759+
app.delete(format!("/rolling_stock/{}", fast_rolling_stock.id).as_str())
1760+
.by_user(admin.as_ref())
1761+
.await
1762+
.assert_status_no_content();
1763+
1764+
// THEN
1765+
let rolling_stock_exists =
1766+
RollingStock::exists(&mut db_pool.get_ok(), fast_rolling_stock.id)
1767+
.await
1768+
.expect("Failed to check if rolling stock exists");
1769+
assert!(!rolling_stock_exists);
1770+
}
1771+
1772+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1773+
async fn delete_rolling_stock_with_skip_authz_without_grant() {
1774+
// GIVEN
1775+
let app = test_app!().skip_authz().build();
1776+
let db_pool = app.db_pool();
1777+
1778+
let fast_rolling_stock =
1779+
create_fast_rolling_stock(&mut db_pool.get_ok(), "skip_authz_deleted_rolling_stock")
1780+
.await;
1781+
1782+
// WHEN (no grant set up, authorization is skipped)
1783+
app.delete(format!("/rolling_stock/{}", fast_rolling_stock.id).as_str())
1784+
.await
1785+
.assert_status_no_content();
1786+
1787+
// THEN
1788+
let rolling_stock_exists =
1789+
RollingStock::exists(&mut db_pool.get_ok(), fast_rolling_stock.id)
1790+
.await
1791+
.expect("Failed to check if rolling stock exists");
1792+
assert!(!rolling_stock_exists);
1793+
}
1794+
1795+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1796+
async fn delete_rolling_stock_without_operational_studies_role() {
1797+
// GIVEN
1798+
let app = test_app!().build();
1799+
let db_pool = app.db_pool();
1800+
1801+
let fast_rolling_stock =
1802+
create_fast_rolling_stock(&mut db_pool.get_ok(), "missing_role_rolling_stock").await;
1803+
1804+
// A user with an owner grant but lacking the OperationalStudies role
1805+
let user = app
1806+
.user("owner", "Owner")
1807+
.with_rolling_stock_grant(fast_rolling_stock.id, RollingStockGrant::Owner)
1808+
.create()
1809+
.await;
1810+
1811+
// WHEN
1812+
app.delete(format!("/rolling_stock/{}", fast_rolling_stock.id).as_str())
1813+
.by_user(user.as_ref())
1814+
.await
1815+
.assert_status_forbidden();
1816+
1817+
// THEN the rolling stock should still exist
1818+
let rolling_stock_exists =
1819+
RollingStock::exists(&mut db_pool.get_ok(), fast_rolling_stock.id)
1820+
.await
1821+
.expect("Failed to check if rolling stock exists");
1822+
assert!(rolling_stock_exists);
1823+
}
1824+
16791825
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
16801826
async fn delete_unlocked_used_rolling_stock_requires_force_flag() {
16811827
// GIVEN

0 commit comments

Comments
 (0)