From e2e9a3efb5ca1ac44ccc6521b8226e6eac8e6554 Mon Sep 17 00:00:00 2001 From: jmanczak Date: Wed, 6 May 2026 18:19:09 +0200 Subject: [PATCH] Admin permission, grant/revoke/reset permission helpers --- src/users/permissions.rs | 81 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 73 insertions(+), 8 deletions(-) diff --git a/src/users/permissions.rs b/src/users/permissions.rs index 2a0e804..1ba9810 100644 --- a/src/users/permissions.rs +++ b/src/users/permissions.rs @@ -3,8 +3,10 @@ use sqlx::PgConnection; use crate::{database::DatabaseError, users::User}; /// Infradmin and systemuser have all permissions. -#[derive(strum::IntoStaticStr)] +#[derive(Debug, Clone, PartialEq, strum::IntoStaticStr)] pub enum Permission { + // Pass all the permission checks + Admin, // All Users have the right to observe their own sessions ListOthersSessions, // All Users have the right to revoke their own sessions @@ -34,15 +36,11 @@ impl Permission { } impl User { - pub async fn has_permission( + async fn permission_dbstate( &self, conn: &mut PgConnection, permission: Permission, - ) -> Result { - if self.is_infradmin() || self.is_systemuser() { - return Ok(true); - } - + ) -> Result, DatabaseError> { let permission_key: &'static str = (&permission).into(); let state: Option = sqlx::query_scalar( "SELECT state FROM user_permissions WHERE user_id = $1 AND permission = $2", @@ -52,6 +50,73 @@ impl User { .fetch_optional(&mut *conn) .await?; - Ok(state.unwrap_or_else(|| permission.is_default_permission())) + Ok(state) + } + + pub async fn has_permission( + &self, + conn: &mut PgConnection, + permission: Permission, + ) -> Result { + if self.is_infradmin() || self.is_systemuser() { + return Ok(true); + } + if let Some(true) = self.permission_dbstate(conn, Permission::Admin).await? { + return Ok(true); + } + + Ok(self + .permission_dbstate(conn, permission) + .await? + .unwrap_or(false)) + } + + pub async fn grant_permission( + &self, + conn: &mut PgConnection, + permission: Permission, + ) -> Result<(), DatabaseError> { + let permission_key: &'static str = (&permission).into(); + sqlx::query( + "INSERT INTO user_permissions (user_id, permission, state) VALUES ($1, $2, TRUE) ON CONFLICT (user_id, permission) DO UPDATE SET state = EXCLUDED.state", + ) + .bind(self.id) + .bind(permission_key) + .execute(&mut *conn) + .await?; + + Ok(()) + } + + pub async fn revoke_permission( + &self, + conn: &mut PgConnection, + permission: Permission, + ) -> Result<(), DatabaseError> { + let permission_key: &'static str = (&permission).into(); + sqlx::query( + "INSERT INTO user_permissions (user_id, permission, state) VALUES ($1, $2, FALSE) ON CONFLICT (user_id, permission) DO UPDATE SET state = EXCLUDED.state", + ) + .bind(self.id) + .bind(permission_key) + .execute(&mut *conn) + .await?; + + Ok(()) + } + + pub async fn reset_permission( + &self, + conn: &mut PgConnection, + permission: Permission, + ) -> Result<(), DatabaseError> { + let permission_key: &'static str = (&permission).into(); + sqlx::query("DELETE FROM user_permissions WHERE user_id = $1 AND permission = $2") + .bind(self.id) + .bind(permission_key) + .execute(&mut *conn) + .await?; + + Ok(()) } }