Rebuild permissions system
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
CREATE TABLE user_permissions(
|
||||
uuid UUID PRIMARY KEY,
|
||||
root BOOL NOT NULL,
|
||||
permissions JSON NOT NULL,
|
||||
permissions JSON NOT NULL
|
||||
);
|
||||
|
||||
@@ -30,14 +30,20 @@ impl AppCfg {
|
||||
&mut self,
|
||||
method: Method,
|
||||
path: impl Into<String>,
|
||||
perms: UserPermissions,
|
||||
root: bool,
|
||||
perms: Vec<UserActions>,
|
||||
) {
|
||||
let key = RouteKey {
|
||||
method,
|
||||
path: path.into(),
|
||||
};
|
||||
|
||||
self.route_perms.insert(key, perms);
|
||||
let user_perms = UserPermissions {
|
||||
root,
|
||||
permissions: perms.into_iter().collect(), // Vec → HashSet
|
||||
};
|
||||
|
||||
self.route_perms.insert(key, user_perms);
|
||||
}
|
||||
|
||||
pub fn get_route_perms(&self, method: &Method, path: &str) -> Option<UserPermissions> {
|
||||
@@ -46,13 +52,19 @@ impl AppCfg {
|
||||
path: path.to_string(),
|
||||
};
|
||||
|
||||
self.route_perms.get(&key)
|
||||
let perm = match self.route_perms.get(&key) {
|
||||
Some(val) => val.clone(),
|
||||
None => return None,
|
||||
};
|
||||
|
||||
Some(perm)
|
||||
}
|
||||
|
||||
pub fn route_allows(&self, method: &Method, path: &str, user_perms: UserPermissions) -> bool {
|
||||
let req_perms = self
|
||||
.get_route_perms(method, path)
|
||||
.ok_or_else(return false)?;
|
||||
let req_perms = match self.get_route_perms(method, path) {
|
||||
Some(val) => val,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
if req_perms.root == true {
|
||||
if user_perms.root == true {
|
||||
|
||||
@@ -1,26 +1,49 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::prelude::FromRow;
|
||||
use sqlx::{prelude::FromRow, types::Json};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||
pub enum UserActions {
|
||||
ManageUsers,
|
||||
Login,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, FromRow)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct UserPermissions {
|
||||
pub root: bool,
|
||||
pub permissions: HashSet<UserActions>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, FromRow)]
|
||||
pub struct UserPermissionsRow {
|
||||
pub root: bool,
|
||||
pub permissions: Json<HashSet<UserActions>>,
|
||||
}
|
||||
|
||||
impl From<UserPermissions> for UserPermissionsRow {
|
||||
fn from(value: UserPermissions) -> Self {
|
||||
Self {
|
||||
root: value.root,
|
||||
permissions: Json(value.permissions),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UserPermissionsRow> for UserPermissions {
|
||||
fn from(value: UserPermissionsRow) -> Self {
|
||||
Self {
|
||||
root: value.root,
|
||||
permissions: value.permissions.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for UserPermissions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
root: false,
|
||||
permissions: Vec::new(),
|
||||
permissions: HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +52,7 @@ impl UserPermissions {
|
||||
pub fn root() -> Self {
|
||||
Self {
|
||||
root: true,
|
||||
permissions: Vec::new(),
|
||||
permissions: HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ use anyhow::Result;
|
||||
use sqlx::PgPool;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{domain::user_prems::UserPermissions, prelude::*};
|
||||
use crate::{
|
||||
domain::user_prems::{UserPermissions, UserPermissionsRow},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
pub async fn create(
|
||||
pool: &PgPool,
|
||||
@@ -10,29 +13,29 @@ pub async fn create(
|
||||
new_perms: UserPermissions,
|
||||
) -> Result<UserPermissions> {
|
||||
debug!(user_uuid = %uuid, "insert user permissions started");
|
||||
let perms = sqlx::query_as::<_, UserPermissions>(
|
||||
let insert = UserPermissionsRow::from(new_perms);
|
||||
let perms = sqlx::query_as::<_, UserPermissionsRow>(
|
||||
r#"
|
||||
INSERT INTO user_permissions (uuid, root, manage_users, login)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING uuid, root, manage_users, login
|
||||
INSERT INTO user_permissions (uuid, root, permissions)
|
||||
VALUES ($1, $2, $3)
|
||||
RETURNING root, permissions
|
||||
"#,
|
||||
)
|
||||
.bind(uuid)
|
||||
.bind(new_perms.root)
|
||||
.bind(new_perms.manage_users)
|
||||
.bind(new_perms.login)
|
||||
.bind(insert.root)
|
||||
.bind(insert.permissions)
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
|
||||
debug!(user_uuid = %uuid, "insert user permissions completed");
|
||||
Ok(perms)
|
||||
Ok(UserPermissions::from(perms))
|
||||
}
|
||||
|
||||
pub async fn get_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<Option<UserPermissions>> {
|
||||
debug!(user_uuid = %uuid, "fetch user permissions by uuid started");
|
||||
let perms = sqlx::query_as::<_, UserPermissions>(
|
||||
let perms = sqlx::query_as::<_, UserPermissionsRow>(
|
||||
r#"
|
||||
SELECT uuid, root, manage_users, login
|
||||
SELECT uuid, root, permissions
|
||||
FROM user_permissions
|
||||
WHERE uuid = $1
|
||||
"#,
|
||||
@@ -42,7 +45,10 @@ pub async fn get_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<Option<UserPermiss
|
||||
.await?;
|
||||
|
||||
debug!(user_uuid = %uuid, "fetch user permissions by uuid completed");
|
||||
Ok(perms)
|
||||
match perms {
|
||||
Some(val) => return Ok(Some(UserPermissions::from(val))),
|
||||
None => return Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn exists_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<bool> {
|
||||
@@ -52,7 +58,7 @@ pub async fn exists_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<bool> {
|
||||
SELECT EXISTS(
|
||||
SELECT 1
|
||||
FROM user_permissions
|
||||
WHERE uuis = $1
|
||||
WHERE uuid = $1
|
||||
)
|
||||
"#,
|
||||
)
|
||||
|
||||
@@ -64,8 +64,12 @@ async fn main() -> Result<()> {
|
||||
route_perms: HashMap::new(),
|
||||
};
|
||||
|
||||
config.insert_route_perms(Method::GET, "/api/login", vec![UserActions::Login]);
|
||||
config.insert_route_perms(Method::GET, "/api/users", vec![UserActions::Login]);
|
||||
config.insert_route_perms(
|
||||
Method::GET,
|
||||
"/api/users",
|
||||
false,
|
||||
vec![UserActions::ManageUsers],
|
||||
);
|
||||
|
||||
let state = Arc::new(AppState::new(config).await);
|
||||
check_root(state.clone()).await;
|
||||
|
||||
@@ -77,7 +77,11 @@ pub async fn permissions(
|
||||
mut req: Request,
|
||||
next: Next,
|
||||
) -> Result<Response, StatusCode> {
|
||||
warn!("Calling into permissions with user {}", user);
|
||||
let request_method = req.method().clone();
|
||||
let request_path = req.uri().path().to_string();
|
||||
|
||||
debug!(method = ?request_method, path = request_path, "permissions request started");
|
||||
debug!("Calling user {}", user.username.clone());
|
||||
let method: Method = req.method().clone();
|
||||
|
||||
let path = req
|
||||
@@ -86,5 +90,11 @@ pub async fn permissions(
|
||||
.map(|p| p.as_str().to_string())
|
||||
.ok_or(StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
Ok(next.run(req).await)
|
||||
match state
|
||||
.config
|
||||
.route_allows(&method, path.as_str(), user.permissions.clone())
|
||||
{
|
||||
true => return Ok(next.run(req).await),
|
||||
false => return Err(StatusCode::UNAUTHORIZED),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -24,11 +24,11 @@ macro_rules! middleware {
|
||||
axum::middleware::from_fn_with_state($state, crate::router::middleware::auth),
|
||||
)
|
||||
};
|
||||
(cors_auth_perms: $state:expr) => {
|
||||
(cors_auth_perms, $state:expr) => {
|
||||
(
|
||||
crate::router::middleware::cors(),
|
||||
axum::middleware::from_fn_with_state($state, crate::router::middleware::auth),
|
||||
axum::middleware::from_fn_with_state($state, crate::router::middleware::perms),
|
||||
axum::middleware::from_fn_with_state($state, crate::router::middleware::permissions),
|
||||
)
|
||||
};
|
||||
}
|
||||
@@ -41,10 +41,10 @@ pub async fn init_router(app_state: Arc<AppState>) -> Router {
|
||||
.route(
|
||||
"/api/users",
|
||||
post(user_routes::create)
|
||||
.layer(middleware!(cors_auth, app_state.clone()))
|
||||
.layer(middleware!(cors_auth_perms, app_state.clone()))
|
||||
.with_state(app_state.clone())
|
||||
.get(user_routes::get_all)
|
||||
.layer(middleware!(cors_auth, app_state.clone()))
|
||||
.layer(middleware!(cors_auth_perms, app_state.clone()))
|
||||
.with_state(app_state.clone()),
|
||||
)
|
||||
.route(
|
||||
|
||||
Reference in New Issue
Block a user