From 20a09a672ad5275742035c719b0db3577ba399fa Mon Sep 17 00:00:00 2001 From: Hector van der Aa Date: Sat, 6 Dec 2025 09:40:42 +0100 Subject: [PATCH] Logout fixes and started perm_esc protection --- src/backend/src/config.rs | 47 +++++++++++++++++++++----- src/backend/src/core/user_routines.rs | 37 ++++++++++++++------- src/backend/src/domain/user.rs | 2 +- src/backend/src/domain/user_prems.rs | 48 +++++++++++++++++++++++++-- src/backend/src/infra/db/perms.rs | 24 +++++++++++++- src/backend/src/main.rs | 6 ++++ src/backend/src/router/middleware.rs | 8 ----- src/backend/src/router/mod.rs | 6 ++-- src/backend/src/state.rs | 11 ++---- 9 files changed, 145 insertions(+), 44 deletions(-) diff --git a/src/backend/src/config.rs b/src/backend/src/config.rs index aaf3e1e..fab7a20 100644 --- a/src/backend/src/config.rs +++ b/src/backend/src/config.rs @@ -1,7 +1,14 @@ -use axum::http::Method; +use axum::{ + Json, RequestExt, + extract::{MatchedPath, Request, State}, + http::{Method, StatusCode}, +}; use std::collections::HashMap; -use crate::domain::user_prems::{UserActions, UserPermissions}; +use crate::domain::{ + user::NewUser, + user_prems::{InternalUserPermissions, UserActions, UserPermissions}, +}; #[derive(Debug, Hash, Clone, PartialEq, Eq)] pub struct RouteKey { @@ -12,7 +19,7 @@ pub struct RouteKey { #[derive(Debug)] pub struct AppCfg { pub db_path: String, - pub route_perms: HashMap, + pub route_perms: HashMap, } impl AppCfg { @@ -29,21 +36,23 @@ impl AppCfg { path: impl Into, root: bool, perms: Vec, + esc_check: bool, ) { let key = RouteKey { method, path: path.into(), }; - let user_perms = UserPermissions { + let user_perms = InternalUserPermissions { root, permissions: perms.into_iter().collect(), // Vec → HashSet + esc_check, }; self.route_perms.insert(key, user_perms); } - pub fn get_route_perms(&self, method: &Method, path: &str) -> Option { + pub fn get_route_perms(&self, method: &Method, path: &str) -> Option { let key = RouteKey { method: method.clone(), path: path.to_string(), @@ -57,19 +66,39 @@ impl AppCfg { Some(perm) } - pub fn route_allows(&self, method: &Method, path: &str, user_perms: UserPermissions) -> bool { + pub async fn route_allows( + &self, + req: Request, + user_perms: UserPermissions, + ) -> Result { + let method = req.method(); + + let path = req + .extensions() + .get::() + .map(|p| p.as_str()) + .ok_or(StatusCode::INTERNAL_SERVER_ERROR)?; let req_perms = match self.get_route_perms(method, path) { Some(val) => val, - None => return false, + None => return Ok(false), }; if req_perms.root { - return user_perms.root + return Ok(true); } - req_perms + match req_perms .permissions .iter() .all(|action| user_perms.permissions.contains(action)) + { + true => (), + false => return Ok(false), + }; + + if req_perms.esc_check { + } else { + Ok(true) + } } } diff --git a/src/backend/src/core/user_routines.rs b/src/backend/src/core/user_routines.rs index 594fc28..f369e13 100644 --- a/src/backend/src/core/user_routines.rs +++ b/src/backend/src/core/user_routines.rs @@ -1,6 +1,10 @@ use crate::{ auth::{gen_jwt, verify_password}, - domain::{api::LoginData, user::InternalUser}, + domain::{ + api::LoginData, + user::InternalUser, + user_prems::{ExtUserPermissions, UserPermissions}, + }, infra::db, prelude::*, }; @@ -71,16 +75,12 @@ pub async fn create(state: Arc, new_user: NewUser) -> Result, new_user: NewUser) -> Result) -> Result, StatusCode> { debug!("fetch all users started"); - let users = db::user::get_safe_all(&state.db_pool).await.map_err(|e| { + let mut users = db::user::get_safe_all(&state.db_pool).await.map_err(|e| { error!(error = %e, "fetch all users failed"); StatusCode::INTERNAL_SERVER_ERROR })?; + let perms = db::perms::get_all(&state.db_pool).await.map_err(|e| { + error!(error = %e, "fetch all permissions failed"); + StatusCode::INTERNAL_SERVER_ERROR + })?; + + users.iter_mut().for_each(|u| { + let u_perms = match perms.iter().find(|p| p.uuid == u.uuid) { + Some(val) => UserPermissions::from(val.clone()), + None => UserPermissions::new(), + }; + u.attach_permissions(u_perms); + }); + Ok(users) } diff --git a/src/backend/src/domain/user.rs b/src/backend/src/domain/user.rs index c8f277a..4e50811 100644 --- a/src/backend/src/domain/user.rs +++ b/src/backend/src/domain/user.rs @@ -5,7 +5,7 @@ use sqlx::prelude::FromRow; use uuid::Uuid; use validator::Validate; -use crate::domain::user_prems::UserPermissions; +use crate::domain::user_prems::{ExtUserPermissions, UserPermissions}; use crate::domain::validation; use crate::auth; diff --git a/src/backend/src/domain/user_prems.rs b/src/backend/src/domain/user_prems.rs index a0a5739..676301d 100644 --- a/src/backend/src/domain/user_prems.rs +++ b/src/backend/src/domain/user_prems.rs @@ -2,25 +2,45 @@ use std::collections::HashSet; use serde::{Deserialize, Serialize}; use sqlx::{prelude::FromRow, types::Json}; +use uuid::Uuid; #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)] pub enum UserActions { ManageUsers, } -#[derive(Debug, Clone, Deserialize, Serialize)] -#[derive(Default)] +#[derive(Debug, Clone, Deserialize, Serialize, Default)] pub struct UserPermissions { pub root: bool, pub permissions: HashSet, } +#[derive(Debug, Clone, Deserialize, Serialize, Default)] +pub struct InternalUserPermissions { + pub root: bool, + pub permissions: HashSet, + pub esc_check: bool, +} +#[derive(Debug, Clone, Deserialize, Serialize, Default)] +pub struct ExtUserPermissions { + pub uuid: Uuid, + pub root: bool, + pub permissions: HashSet, +} + #[derive(Debug, Clone, FromRow)] pub struct UserPermissionsRow { pub root: bool, pub permissions: Json>, } +#[derive(Debug, Clone, FromRow)] +pub struct ExtUserPermissionsRow { + pub uuid: Uuid, + pub root: bool, + pub permissions: Json>, +} + impl From for UserPermissionsRow { fn from(value: UserPermissions) -> Self { Self { @@ -39,8 +59,32 @@ impl From for UserPermissions { } } +impl From for ExtUserPermissions { + fn from(value: ExtUserPermissionsRow) -> Self { + Self { + uuid: value.uuid, + root: value.root, + permissions: value.permissions.0, + } + } +} + +impl From for UserPermissions { + fn from(value: ExtUserPermissions) -> Self { + Self { + root: value.root, + permissions: value.permissions, + } + } +} impl UserPermissions { + pub fn new() -> Self { + Self { + root: false, + permissions: HashSet::new(), + } + } pub fn root() -> Self { Self { root: true, diff --git a/src/backend/src/infra/db/perms.rs b/src/backend/src/infra/db/perms.rs index f9d4b7b..6ae7cf7 100644 --- a/src/backend/src/infra/db/perms.rs +++ b/src/backend/src/infra/db/perms.rs @@ -3,7 +3,9 @@ use sqlx::PgPool; use uuid::Uuid; use crate::{ - domain::user_prems::{UserPermissions, UserPermissionsRow}, + domain::user_prems::{ + ExtUserPermissions, ExtUserPermissionsRow, UserPermissions, UserPermissionsRow, + }, prelude::*, }; @@ -69,3 +71,23 @@ pub async fn exists_by_uuid(pool: &PgPool, uuid: Uuid) -> Result { debug!(user_uuid = %uuid, "check user permissions existence completed"); Ok(exists) } + +pub async fn get_all(pool: &PgPool) -> Result> { + debug!("fetch all internal users started"); + let users = sqlx::query_as::<_, ExtUserPermissionsRow>( + r#" + SELECT uuid, root, permissions + FROM user_permissions + "#, + ) + .fetch_all(pool) + .await?; + + let clean = users + .iter() + .map(|v| ExtUserPermissions::from(v.clone())) + .collect(); + + debug!("fetch all internal users completed"); + Ok(clean) +} diff --git a/src/backend/src/main.rs b/src/backend/src/main.rs index e0899ab..ffb95c1 100644 --- a/src/backend/src/main.rs +++ b/src/backend/src/main.rs @@ -69,6 +69,12 @@ async fn main() -> Result<()> { false, vec![UserActions::ManageUsers], ); + config.insert_route_perms( + Method::POST, + "/api/users", + false, + vec![UserActions::ManageUsers], + ); let state = Arc::new(AppState::new(config).await); check_root(state.clone()).await; diff --git a/src/backend/src/router/middleware.rs b/src/backend/src/router/middleware.rs index 83a91e4..1d422bd 100644 --- a/src/backend/src/router/middleware.rs +++ b/src/backend/src/router/middleware.rs @@ -121,14 +121,6 @@ pub async fn permissions( return Ok(next.run(req).await); } - let method: Method = req.method().clone(); - - let path = req - .extensions() - .get::() - .map(|p| p.as_str().to_string()) - .ok_or(StatusCode::INTERNAL_SERVER_ERROR)?; - match state .config .route_allows(&method, path.as_str(), user.permissions.clone()) diff --git a/src/backend/src/router/mod.rs b/src/backend/src/router/mod.rs index 2ac0621..4da99e1 100644 --- a/src/backend/src/router/mod.rs +++ b/src/backend/src/router/mod.rs @@ -48,7 +48,7 @@ pub async fn init_router(app_state: Arc) -> Router { .route( "/api/users/{uuid}", get(user_routes::get_uuid) - .layer(middleware!(cors_auth, app_state.clone())) + .layer(middleware!(cors_auth_perms, app_state.clone())) .with_state(app_state.clone()), ) .route( @@ -59,7 +59,9 @@ pub async fn init_router(app_state: Arc) -> Router { ) .route( "/api/logout", - post(user_routes::logout).layer(middleware!(cors)), + post(user_routes::logout) + .layer(middleware!(cors_auth, app_state.clone())) + .with_state(app_state.clone()), ); info!("router initialization completed"); diff --git a/src/backend/src/state.rs b/src/backend/src/state.rs index 074a356..6b37703 100644 --- a/src/backend/src/state.rs +++ b/src/backend/src/state.rs @@ -1,10 +1,6 @@ use std::{process::exit, sync::Arc}; -use crate::{ - core, - domain::user::NewUser, - prelude::*, -}; +use crate::{core, domain::user::NewUser, prelude::*}; use sqlx::PgPool; @@ -37,10 +33,7 @@ impl AppState { .unwrap(); info!("database ready after connect and migrate"); - Self { - db_pool, - config, - } + Self { db_pool, config } } }