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 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)]
|
#[derive(Debug, Hash, Clone, PartialEq, Eq)]
|
||||||
pub struct RouteKey {
|
pub struct RouteKey {
|
||||||
@@ -12,7 +19,7 @@ pub struct RouteKey {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AppCfg {
|
pub struct AppCfg {
|
||||||
pub db_path: String,
|
pub db_path: String,
|
||||||
pub route_perms: HashMap<RouteKey, UserPermissions>,
|
pub route_perms: HashMap<RouteKey, InternalUserPermissions>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppCfg {
|
impl AppCfg {
|
||||||
@@ -29,21 +36,23 @@ impl AppCfg {
|
|||||||
path: impl Into<String>,
|
path: impl Into<String>,
|
||||||
root: bool,
|
root: bool,
|
||||||
perms: Vec<UserActions>,
|
perms: Vec<UserActions>,
|
||||||
|
esc_check: bool,
|
||||||
) {
|
) {
|
||||||
let key = RouteKey {
|
let key = RouteKey {
|
||||||
method,
|
method,
|
||||||
path: path.into(),
|
path: path.into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_perms = UserPermissions {
|
let user_perms = InternalUserPermissions {
|
||||||
root,
|
root,
|
||||||
permissions: perms.into_iter().collect(), // Vec → HashSet
|
permissions: perms.into_iter().collect(), // Vec → HashSet
|
||||||
|
esc_check,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.route_perms.insert(key, user_perms);
|
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 {
|
let key = RouteKey {
|
||||||
method: method.clone(),
|
method: method.clone(),
|
||||||
path: path.to_string(),
|
path: path.to_string(),
|
||||||
@@ -57,19 +66,39 @@ impl AppCfg {
|
|||||||
Some(perm)
|
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) {
|
let req_perms = match self.get_route_perms(method, path) {
|
||||||
Some(val) => val,
|
Some(val) => val,
|
||||||
None => return false,
|
None => return Ok(false),
|
||||||
};
|
};
|
||||||
|
|
||||||
if req_perms.root {
|
if req_perms.root {
|
||||||
return user_perms.root
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
req_perms
|
match req_perms
|
||||||
.permissions
|
.permissions
|
||||||
.iter()
|
.iter()
|
||||||
.all(|action| user_perms.permissions.contains(action))
|
.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::{
|
use crate::{
|
||||||
auth::{gen_jwt, verify_password},
|
auth::{gen_jwt, verify_password},
|
||||||
domain::{api::LoginData, user::InternalUser},
|
domain::{
|
||||||
|
api::LoginData,
|
||||||
|
user::InternalUser,
|
||||||
|
user_prems::{ExtUserPermissions, UserPermissions},
|
||||||
|
},
|
||||||
infra::db,
|
infra::db,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
@@ -71,16 +75,12 @@ pub async fn create(state: Arc<AppState>, new_user: NewUser) -> Result<User, Sta
|
|||||||
StatusCode::INTERNAL_SERVER_ERROR
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let perms = db::perms::create(
|
let perms = db::perms::create(&state.db_pool, created_user.uuid, internal.permissions)
|
||||||
&state.db_pool,
|
.await
|
||||||
created_user.uuid,
|
.map_err(|e| {
|
||||||
internal.permissions,
|
error!(error = %e, "create user permissions entry failed");
|
||||||
)
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
.await
|
})?;
|
||||||
.map_err(|e| {
|
|
||||||
error!(error = %e, "create user permissions entry failed");
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR
|
|
||||||
})?;
|
|
||||||
|
|
||||||
created_user.attach_permissions(perms);
|
created_user.attach_permissions(perms);
|
||||||
|
|
||||||
@@ -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> {
|
pub async fn get_all(state: Arc<AppState>) -> Result<Vec<User>, StatusCode> {
|
||||||
debug!("fetch all users started");
|
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");
|
error!(error = %e, "fetch all users failed");
|
||||||
StatusCode::INTERNAL_SERVER_ERROR
|
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)
|
Ok(users)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use sqlx::prelude::FromRow;
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
use crate::domain::user_prems::UserPermissions;
|
use crate::domain::user_prems::{ExtUserPermissions, UserPermissions};
|
||||||
use crate::domain::validation;
|
use crate::domain::validation;
|
||||||
|
|
||||||
use crate::auth;
|
use crate::auth;
|
||||||
|
|||||||
@@ -2,25 +2,45 @@ use std::collections::HashSet;
|
|||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::{prelude::FromRow, types::Json};
|
use sqlx::{prelude::FromRow, types::Json};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||||
pub enum UserActions {
|
pub enum UserActions {
|
||||||
ManageUsers,
|
ManageUsers,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||||
#[derive(Default)]
|
|
||||||
pub struct UserPermissions {
|
pub struct UserPermissions {
|
||||||
pub root: bool,
|
pub root: bool,
|
||||||
pub permissions: HashSet<UserActions>,
|
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)]
|
#[derive(Debug, Clone, FromRow)]
|
||||||
pub struct UserPermissionsRow {
|
pub struct UserPermissionsRow {
|
||||||
pub root: bool,
|
pub root: bool,
|
||||||
pub permissions: Json<HashSet<UserActions>>,
|
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 {
|
impl From<UserPermissions> for UserPermissionsRow {
|
||||||
fn from(value: UserPermissions) -> Self {
|
fn from(value: UserPermissions) -> Self {
|
||||||
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 {
|
impl UserPermissions {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
root: false,
|
||||||
|
permissions: HashSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn root() -> Self {
|
pub fn root() -> Self {
|
||||||
Self {
|
Self {
|
||||||
root: true,
|
root: true,
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ use sqlx::PgPool;
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
domain::user_prems::{UserPermissions, UserPermissionsRow},
|
domain::user_prems::{
|
||||||
|
ExtUserPermissions, ExtUserPermissionsRow, UserPermissions, UserPermissionsRow,
|
||||||
|
},
|
||||||
prelude::*,
|
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");
|
debug!(user_uuid = %uuid, "check user permissions existence completed");
|
||||||
Ok(exists)
|
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,
|
false,
|
||||||
vec![UserActions::ManageUsers],
|
vec![UserActions::ManageUsers],
|
||||||
);
|
);
|
||||||
|
config.insert_route_perms(
|
||||||
|
Method::POST,
|
||||||
|
"/api/users",
|
||||||
|
false,
|
||||||
|
vec![UserActions::ManageUsers],
|
||||||
|
);
|
||||||
|
|
||||||
let state = Arc::new(AppState::new(config).await);
|
let state = Arc::new(AppState::new(config).await);
|
||||||
check_root(state.clone()).await;
|
check_root(state.clone()).await;
|
||||||
|
|||||||
@@ -121,14 +121,6 @@ pub async fn permissions(
|
|||||||
return Ok(next.run(req).await);
|
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
|
match state
|
||||||
.config
|
.config
|
||||||
.route_allows(&method, path.as_str(), user.permissions.clone())
|
.route_allows(&method, path.as_str(), user.permissions.clone())
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ pub async fn init_router(app_state: Arc<AppState>) -> Router {
|
|||||||
.route(
|
.route(
|
||||||
"/api/users/{uuid}",
|
"/api/users/{uuid}",
|
||||||
get(user_routes::get_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()),
|
.with_state(app_state.clone()),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
@@ -59,7 +59,9 @@ pub async fn init_router(app_state: Arc<AppState>) -> Router {
|
|||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/api/logout",
|
"/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");
|
info!("router initialization completed");
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
use std::{process::exit, sync::Arc};
|
use std::{process::exit, sync::Arc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{core, domain::user::NewUser, prelude::*};
|
||||||
core,
|
|
||||||
domain::user::NewUser,
|
|
||||||
prelude::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
@@ -37,10 +33,7 @@ impl AppState {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
info!("database ready after connect and migrate");
|
info!("database ready after connect and migrate");
|
||||||
|
|
||||||
Self {
|
Self { db_pool, config }
|
||||||
db_pool,
|
|
||||||
config,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user