From ab18e391b5734d1411a46486c0b484805181a347 Mon Sep 17 00:00:00 2001 From: Nikhil Sinha Date: Mon, 20 Oct 2025 15:40:41 +0100 Subject: [PATCH 1/2] add admin authorizations to dashboards and filters allow admins to get, update and delete dashboards and filters --- src/handlers/http/users/dashboards.rs | 15 +++++++++++---- src/handlers/http/users/filters.rs | 20 +++++++++++++++----- src/users/dashboards.rs | 9 ++++++--- src/users/filters.rs | 10 ++++++++-- src/utils/mod.rs | 13 +++++++++++++ 5 files changed, 53 insertions(+), 14 deletions(-) diff --git a/src/handlers/http/users/dashboards.rs b/src/handlers/http/users/dashboards.rs index 38aad3fa9..be894da5b 100644 --- a/src/handlers/http/users/dashboards.rs +++ b/src/handlers/http/users/dashboards.rs @@ -23,7 +23,7 @@ use crate::{ metastore::MetastoreError, storage::ObjectStorageError, users::dashboards::{DASHBOARDS, Dashboard, Tile, validate_dashboard_id}, - utils::{get_hash, get_user_from_request}, + utils::{get_hash, get_user_from_request, is_admin}, }; use actix_web::{ HttpRequest, HttpResponse, Responder, @@ -104,8 +104,10 @@ pub async fn update_dashboard( ) -> Result { let user_id = get_hash(&get_user_from_request(&req)?); let dashboard_id = validate_dashboard_id(dashboard_id.into_inner())?; + let is_admin = is_admin(&req).map_err(|e| DashboardError::Custom(e.to_string()))?; + let mut existing_dashboard = DASHBOARDS - .get_dashboard_by_user(dashboard_id, &user_id) + .get_dashboard_by_user(dashboard_id, &user_id, is_admin) .await .ok_or(DashboardError::Metadata( "Dashboard does not exist or user is not authorized", @@ -189,9 +191,13 @@ pub async fn delete_dashboard( dashboard_id: Path, ) -> Result { let user_id = get_hash(&get_user_from_request(&req)?); + let is_admin = is_admin(&req).map_err(|e| DashboardError::Custom(e.to_string()))?; + let dashboard_id = validate_dashboard_id(dashboard_id.into_inner())?; - DASHBOARDS.delete_dashboard(&user_id, dashboard_id).await?; + DASHBOARDS + .delete_dashboard(&user_id, dashboard_id, is_admin) + .await?; Ok(HttpResponse::Ok().finish()) } @@ -207,9 +213,10 @@ pub async fn add_tile( let user_id = get_hash(&get_user_from_request(&req)?); let dashboard_id = validate_dashboard_id(dashboard_id.into_inner())?; + let is_admin = is_admin(&req).map_err(|e| DashboardError::Custom(e.to_string()))?; let mut dashboard = DASHBOARDS - .get_dashboard_by_user(dashboard_id, &user_id) + .get_dashboard_by_user(dashboard_id, &user_id, is_admin) .await .ok_or(DashboardError::Unauthorized)?; diff --git a/src/handlers/http/users/filters.rs b/src/handlers/http/users/filters.rs index 98559a910..629d1ded2 100644 --- a/src/handlers/http/users/filters.rs +++ b/src/handlers/http/users/filters.rs @@ -22,7 +22,7 @@ use crate::{ parseable::PARSEABLE, storage::ObjectStorageError, users::filters::{CURRENT_FILTER_VERSION, FILTERS, Filter}, - utils::{actix::extract_session_key_from_req, get_hash, get_user_from_request}, + utils::{actix::extract_session_key_from_req, get_hash, get_user_from_request, is_admin}, }; use actix_web::{ HttpRequest, HttpResponse, Responder, @@ -46,8 +46,11 @@ pub async fn get( ) -> Result { let user_id = get_user_from_request(&req)?; let filter_id = filter_id.into_inner(); - - if let Some(filter) = FILTERS.get_filter(&filter_id, &get_hash(&user_id)).await { + let is_admin = is_admin(&req).map_err(|e| FiltersError::Custom(e.to_string()))?; + if let Some(filter) = FILTERS + .get_filter(&filter_id, &get_hash(&user_id), is_admin) + .await + { return Ok((web::Json(filter), StatusCode::OK)); } @@ -81,7 +84,13 @@ pub async fn update( let mut user_id = get_user_from_request(&req)?; user_id = get_hash(&user_id); let filter_id = filter_id.into_inner(); - if FILTERS.get_filter(&filter_id, &user_id).await.is_none() { + let is_admin = is_admin(&req).map_err(|e| FiltersError::Custom(e.to_string()))?; + + if FILTERS + .get_filter(&filter_id, &user_id, is_admin) + .await + .is_none() + { return Err(FiltersError::Metadata( "Filter does not exist or user is not authorized", )); @@ -103,8 +112,9 @@ pub async fn delete( let mut user_id = get_user_from_request(&req)?; user_id = get_hash(&user_id); let filter_id = filter_id.into_inner(); + let is_admin = is_admin(&req).map_err(|e| FiltersError::Custom(e.to_string()))?; let filter = FILTERS - .get_filter(&filter_id, &user_id) + .get_filter(&filter_id, &user_id, is_admin) .await .ok_or(FiltersError::Metadata( "Filter does not exist or user is not authorized", diff --git a/src/users/dashboards.rs b/src/users/dashboards.rs index fbf83fda6..d2e21a05a 100644 --- a/src/users/dashboards.rs +++ b/src/users/dashboards.rs @@ -295,9 +295,10 @@ impl Dashboards { &self, user_id: &str, dashboard_id: Ulid, + is_admin: bool, ) -> Result<(), DashboardError> { let obj = self - .ensure_dashboard_ownership(dashboard_id, user_id) + .ensure_dashboard_ownership(dashboard_id, user_id, is_admin) .await?; { @@ -335,6 +336,7 @@ impl Dashboards { &self, dashboard_id: Ulid, user_id: &str, + is_admin: bool, ) -> Option { self.0 .read() @@ -344,7 +346,7 @@ impl Dashboards { d.dashboard_id .as_ref() .is_some_and(|id| *id == dashboard_id) - && d.author == Some(user_id.to_string()) + && (d.author == Some(user_id.to_string()) || is_admin) }) .cloned() } @@ -409,8 +411,9 @@ impl Dashboards { &self, dashboard_id: Ulid, user_id: &str, + is_admin: bool, ) -> Result { - self.get_dashboard_by_user(dashboard_id, user_id) + self.get_dashboard_by_user(dashboard_id, user_id, is_admin) .await .ok_or_else(|| { DashboardError::Metadata( diff --git a/src/users/filters.rs b/src/users/filters.rs index b8cabc34f..627ece52b 100644 --- a/src/users/filters.rs +++ b/src/users/filters.rs @@ -132,13 +132,19 @@ impl Filters { s.retain(|f| f.filter_id != Some(filter_id.to_string())); } - pub async fn get_filter(&self, filter_id: &str, user_id: &str) -> Option { + pub async fn get_filter( + &self, + filter_id: &str, + user_id: &str, + is_admin: bool, + ) -> Option { self.0 .read() .await .iter() .find(|f| { - f.filter_id == Some(filter_id.to_string()) && f.user_id == Some(user_id.to_string()) + f.filter_id == Some(filter_id.to_string()) + && (f.user_id == Some(user_id.to_string()) || is_admin) }) .cloned() } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index fc3290ab4..2f1f7cc03 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -145,3 +145,16 @@ pub async fn user_auth_for_datasets( Ok(()) } + +pub fn is_admin(req: &HttpRequest) -> Result { + let session_key = + extract_session_key_from_req(req).map_err(|e| anyhow::Error::msg(e.to_string()))?; + let userid = if let Some(u) = Users.get_userid_from_session(&session_key) { + u + } else { + return Err(anyhow::Error::msg("User not found")); + }; + let permissions = Users.get_role(&userid); + + Ok(permissions.contains(&String::from("admin"))) +} From ad97ff965d862b18e9a0836cb9f4535ae7a0c6e3 Mon Sep 17 00:00:00 2001 From: Nikhil Sinha Date: Mon, 20 Oct 2025 16:53:30 +0100 Subject: [PATCH 2/2] update is_admin logic --- src/utils/mod.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 2f1f7cc03..b0a49d201 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -149,12 +149,18 @@ pub async fn user_auth_for_datasets( pub fn is_admin(req: &HttpRequest) -> Result { let session_key = extract_session_key_from_req(req).map_err(|e| anyhow::Error::msg(e.to_string()))?; - let userid = if let Some(u) = Users.get_userid_from_session(&session_key) { - u - } else { - return Err(anyhow::Error::msg("User not found")); - }; - let permissions = Users.get_role(&userid); - Ok(permissions.contains(&String::from("admin"))) + let permissions = Users.get_permissions(&session_key); + + // Check if user has admin permissions (Action::All on All resources) + for permission in permissions.iter() { + match permission { + Permission::Resource(Action::All, ParseableResourceType::All) => { + return Ok(true); + } + _ => continue, + } + } + + Ok(false) }