Logout fixes and started perm_esc protection
This commit is contained in:
@@ -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<RouteKey, UserPermissions>,
|
||||
pub route_perms: HashMap<RouteKey, InternalUserPermissions>,
|
||||
}
|
||||
|
||||
impl AppCfg {
|
||||
@@ -29,21 +36,23 @@ impl AppCfg {
|
||||
path: impl Into<String>,
|
||||
root: bool,
|
||||
perms: Vec<UserActions>,
|
||||
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<UserPermissions> {
|
||||
pub fn get_route_perms(&self, method: &Method, path: &str) -> Option<InternalUserPermissions> {
|
||||
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<bool, StatusCode> {
|
||||
let method = req.method();
|
||||
|
||||
let path = req
|
||||
.extensions()
|
||||
.get::<MatchedPath>()
|
||||
.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,11 +75,7 @@ pub async fn create(state: Arc<AppState>, new_user: NewUser) -> Result<User, Sta
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
|
||||
let perms = db::perms::create(
|
||||
&state.db_pool,
|
||||
created_user.uuid,
|
||||
internal.permissions,
|
||||
)
|
||||
let perms = db::perms::create(&state.db_pool, created_user.uuid, internal.permissions)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!(error = %e, "create user permissions entry failed");
|
||||
@@ -92,11 +92,24 @@ pub async fn create(state: Arc<AppState>, new_user: NewUser) -> Result<User, Sta
|
||||
|
||||
pub async fn get_all(state: Arc<AppState>) -> Result<Vec<User>, 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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<UserActions>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||
pub struct InternalUserPermissions {
|
||||
pub root: bool,
|
||||
pub permissions: HashSet<UserActions>,
|
||||
pub esc_check: bool,
|
||||
}
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||
pub struct ExtUserPermissions {
|
||||
pub uuid: Uuid,
|
||||
pub root: bool,
|
||||
pub permissions: HashSet<UserActions>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, FromRow)]
|
||||
pub struct UserPermissionsRow {
|
||||
pub root: bool,
|
||||
pub permissions: Json<HashSet<UserActions>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, FromRow)]
|
||||
pub struct ExtUserPermissionsRow {
|
||||
pub uuid: Uuid,
|
||||
pub root: bool,
|
||||
pub permissions: Json<HashSet<UserActions>>,
|
||||
}
|
||||
|
||||
impl From<UserPermissions> for UserPermissionsRow {
|
||||
fn from(value: UserPermissions) -> Self {
|
||||
Self {
|
||||
@@ -39,8 +59,32 @@ impl From<UserPermissionsRow> for UserPermissions {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ExtUserPermissionsRow> for ExtUserPermissions {
|
||||
fn from(value: ExtUserPermissionsRow) -> Self {
|
||||
Self {
|
||||
uuid: value.uuid,
|
||||
root: value.root,
|
||||
permissions: value.permissions.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ExtUserPermissions> 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,
|
||||
|
||||
@@ -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<bool> {
|
||||
debug!(user_uuid = %uuid, "check user permissions existence completed");
|
||||
Ok(exists)
|
||||
}
|
||||
|
||||
pub async fn get_all(pool: &PgPool) -> Result<Vec<ExtUserPermissions>> {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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::<MatchedPath>()
|
||||
.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())
|
||||
|
||||
@@ -48,7 +48,7 @@ pub async fn init_router(app_state: Arc<AppState>) -> 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<AppState>) -> 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");
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user