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