diff --git a/src/common/meta/user.rs b/src/common/meta/user.rs index b628fa8e99..78aff0ffd0 100644 --- a/src/common/meta/user.rs +++ b/src/common/meta/user.rs @@ -200,6 +200,9 @@ pub enum UserRole { #[cfg(feature = "enterprise")] #[serde(rename = "user")] User, + #[cfg(feature = "enterprise")] + #[serde(rename = "editor")] + Editor, } impl fmt::Display for UserRole { @@ -211,6 +214,8 @@ impl fmt::Display for UserRole { #[cfg(feature = "enterprise")] UserRole::Viewer => write!(f, "viewer"), #[cfg(feature = "enterprise")] + UserRole::Editor => write!(f, "editor"), + #[cfg(feature = "enterprise")] UserRole::User => write!(f, "user"), } } @@ -228,6 +233,8 @@ impl FromStr for UserRole { #[cfg(feature = "enterprise")] "viewer" => Ok(UserRole::Viewer), #[cfg(feature = "enterprise")] + "editor" => Ok(UserRole::Editor), + #[cfg(feature = "enterprise")] "user" => Ok(UserRole::User), #[cfg(feature = "enterprise")] _ => Ok(UserRole::User), diff --git a/src/common/utils/auth.rs b/src/common/utils/auth.rs index 2580b81d04..5e4d15074d 100644 --- a/src/common/utils/auth.rs +++ b/src/common/utils/auth.rs @@ -123,6 +123,7 @@ pub struct AuthExtractor { pub o2_type: String, pub org_id: String, pub bypass_check: bool, + pub parent_id: String, } impl FromRequest for AuthExtractor { @@ -141,6 +142,14 @@ impl FromRequest for AuthExtractor { use crate::common::utils::http::{get_folder, get_stream_type_from_request}; + let query = web::Query::>::from_query(req.query_string()).unwrap(); + let stream_type = match get_stream_type_from_request(&query) { + Ok(v) => v, + Err(_) => Some(StreamType::Logs), + }; + + let folder = get_folder(&query); + let mut method = req.method().to_string(); let local_path = req.path().to_string(); let path = @@ -162,6 +171,7 @@ impl FromRequest for AuthExtractor { o2_type: format!("stream:{org_id}"), org_id, bypass_check: true, + parent_id: folder, })); } } @@ -191,7 +201,33 @@ impl FromRequest for AuthExtractor { path_columns[url_len - 2] ) } else if url_len == 3 { - if method.eq("PUT") || method.eq("DELETE") { + if path_columns[2].starts_with("alerts") { + if method.eq("PUT") || method.eq("DELETE") { + format!( + "{}:{}", + OFGA_MODELS + .get(path_columns[1]) + .map_or(path_columns[1], |model| model.key), + path_columns[2] + ) + } else { + format!( + "{}:{}", + OFGA_MODELS + .get(path_columns[2]) + .map_or(path_columns[2], |model| model.key), + path_columns[0] + ) + } + } else if path_columns[2].starts_with("_values") + || path_columns[2].starts_with("_around") + { + format!( + "{}:{}", + OFGA_MODELS.get("streams").unwrap().key, + path_columns[1] + ) + } else if method.eq("PUT") || method.eq("DELETE") { format!( "{}:{}", OFGA_MODELS @@ -208,17 +244,45 @@ impl FromRequest for AuthExtractor { path_columns[0] ) } + } else if url_len == 4 { + if method.eq("PUT") || method.eq("DELETE") { + format!( + "{}:{}", + OFGA_MODELS + .get(path_columns[2]) + .map_or(path_columns[2], |model| model.key), + path_columns[3] + ) + } else { + format!( + "{}:{}", + OFGA_MODELS + .get(path_columns[1]) + .map_or(path_columns[1], |model| model.key), + path_columns[2] + ) + } } else if method.eq("PUT") || method.eq("DELETE") { if path_columns[url_len - 1].eq("delete_fields") { method = "DELETE".to_string(); } - format!( - "{}:{}", - OFGA_MODELS - .get(path_columns[1]) - .map_or(path_columns[1], |model| model.key), - path_columns[2] - ) + if path_columns[url_len - 1].eq("enable") { + format!( + "{}:{}", + OFGA_MODELS + .get(path_columns[2]) + .map_or(path_columns[2], |model| model.key), + path_columns[3] + ) + } else { + format!( + "{}:{}", + OFGA_MODELS + .get(path_columns[1]) + .map_or(path_columns[1], |model| model.key), + path_columns[2] + ) + } } else { format!( "{}:{}", @@ -229,14 +293,6 @@ impl FromRequest for AuthExtractor { ) }; - let query = web::Query::>::from_query(req.query_string()).unwrap(); - let stream_type = match get_stream_type_from_request(&query) { - Ok(v) => v, - Err(_) => Some(StreamType::Logs), - }; - - let folder = get_folder(&query); - if let Some(auth_header) = req.headers().get("Authorization") { if let Ok(auth_str) = auth_header.to_str() { if (method.eq("POST") && path_columns[1].starts_with("_search")) @@ -249,6 +305,7 @@ impl FromRequest for AuthExtractor { o2_type: "".to_string(), org_id: "".to_string(), bypass_check: true, // bypass check permissions + parent_id: folder, })); } else if object_type.starts_with("stream") && !method.eq("LIST") { let object_type = match stream_type { @@ -262,10 +319,20 @@ impl FromRequest for AuthExtractor { o2_type: object_type, org_id, bypass_check: false, + parent_id: folder, })); } else if object_type.contains("dashboard") && !method.eq("LIST") { - let object_type = object_type - .replace("dashboard:", format!("dashboard:{}/", folder).as_str()); + let object_type = if method.eq("POST") { + format!( + "{}:{}", + OFGA_MODELS + .get(path_columns[1]) + .map_or("dfolder", |model| model.parent), + folder.as_str(), + ) + } else { + object_type + }; return ready(Ok(AuthExtractor { auth: auth_str.to_owned(), @@ -273,6 +340,7 @@ impl FromRequest for AuthExtractor { o2_type: object_type, org_id, bypass_check: false, + parent_id: folder, })); } @@ -282,6 +350,7 @@ impl FromRequest for AuthExtractor { o2_type: object_type, org_id, bypass_check: false, + parent_id: folder, })); } } @@ -301,6 +370,7 @@ impl FromRequest for AuthExtractor { o2_type: "".to_string(), org_id: "".to_string(), bypass_check: true, // bypass check permissions + parent_id: "".to_string(), })); } } diff --git a/src/handler/http/auth/validator.rs b/src/handler/http/auth/validator.rs index 0f66c05b96..efb621f5ba 100644 --- a/src/handler/http/auth/validator.rs +++ b/src/handler/http/auth/validator.rs @@ -405,8 +405,14 @@ pub(crate) async fn check_permissions(user_id: &str, auth_info: AuthExtractor) - } else { object_str }; - o2_enterprise::enterprise::openfga::authorizer::is_allowed(user_id, &auth_info.method, &obj_str) - .await + o2_enterprise::enterprise::openfga::authorizer::is_allowed( + &auth_info.org_id, + user_id, + &auth_info.method, + &obj_str, + &auth_info.parent_id, + ) + .await } #[cfg(not(feature = "enterprise"))] diff --git a/src/handler/http/request/prom/mod.rs b/src/handler/http/request/prom/mod.rs index b47db878c4..b8b378e204 100644 --- a/src/handler/http/request/prom/mod.rs +++ b/src/handler/http/request/prom/mod.rs @@ -172,6 +172,7 @@ async fn query( o2_type: format!("stream:{}/{}", "metrics", name), org_id: org_id.to_string(), bypass_check: false, + parent_id: "".to_string(), }, ) .await @@ -322,6 +323,7 @@ async fn query_range( o2_type: format!("stream:{}/{}", "metrics", name), org_id: org_id.to_string(), bypass_check: false, + parent_id: "".to_string(), }, ) .await diff --git a/src/handler/http/request/search/mod.rs b/src/handler/http/request/search/mod.rs index 20aa50eb24..3f19299b97 100644 --- a/src/handler/http/request/search/mod.rs +++ b/src/handler/http/request/search/mod.rs @@ -159,6 +159,7 @@ pub async fn search( o2_type: format!("stream:{}/{}", stream_type, resp.stream_name), org_id: org_id.clone(), bypass_check: false, + parent_id: "".to_string(), }, ) .await diff --git a/src/handler/http/request/search/saved_view.rs b/src/handler/http/request/search/saved_view.rs index fd17cc8357..b10785438b 100644 --- a/src/handler/http/request/search/saved_view.rs +++ b/src/handler/http/request/search/saved_view.rs @@ -18,11 +18,15 @@ use std::io::Error; use actix_web::{delete, get, post, put, web, HttpResponse}; use crate::{ - common::meta::{ - http::HttpResponse as MetaHttpResponse, - saved_view::{ - CreateViewRequest, CreateViewResponse, DeleteViewResponse, UpdateViewRequest, View, + common::{ + meta::{ + authz::Authz, + http::HttpResponse as MetaHttpResponse, + saved_view::{ + CreateViewRequest, CreateViewResponse, DeleteViewResponse, UpdateViewRequest, View, + }, }, + utils::auth::{remove_ownership, set_ownership}, }, service::db::saved_view, }; @@ -128,10 +132,13 @@ pub async fn get_views(path: web::Path) -> Result { pub async fn delete_view(path: web::Path<(String, String)>) -> Result { let (org_id, view_id) = path.into_inner(); match saved_view::delete_view(&org_id, &view_id).await { - Ok(_) => Ok(MetaHttpResponse::json(DeleteViewResponse { - org_id, - view_id, - })), + Ok(_) => { + remove_ownership(&org_id, "savedviews", Authz::new(&view_id)).await; + Ok(MetaHttpResponse::json(DeleteViewResponse { + org_id, + view_id, + })) + } Err(e) => Ok(MetaHttpResponse::bad_request(e)), } } @@ -168,11 +175,14 @@ pub async fn create_view( let org_id = path.into_inner(); match saved_view::set_view(&org_id, &view).await { - Ok(created_view) => Ok(MetaHttpResponse::json(CreateViewResponse { - org_id, - view_id: created_view.view_id, - view_name: view.view_name.clone(), - })), + Ok(created_view) => { + set_ownership(&org_id, "savedviews", Authz::new(&created_view.view_id)).await; + Ok(MetaHttpResponse::json(CreateViewResponse { + org_id, + view_id: created_view.view_id, + view_name: view.view_name.clone(), + })) + } Err(e) => Ok(MetaHttpResponse::bad_request(e)), } } diff --git a/web/src/composables/useLogs.ts b/web/src/composables/useLogs.ts index bce7ae89d6..52820e3318 100644 --- a/web/src/composables/useLogs.ts +++ b/web/src/composables/useLogs.ts @@ -1422,6 +1422,7 @@ const useLogs = () => { size: obj.size, query_context: query_context, query_fn: query_fn, + stream_type: searchObj.data.stream.streamType, }) .then((res) => { searchObj.loading = false; diff --git a/web/src/services/search.ts b/web/src/services/search.ts index e1fdf04993..9772717196 100644 --- a/web/src/services/search.ts +++ b/web/src/services/search.ts @@ -35,6 +35,7 @@ const search = { size, query_context, query_fn, + stream_type, }: { org_identifier: string; index: string; @@ -42,8 +43,9 @@ const search = { size: string; query_context: any; query_fn: any; + stream_type: string; }) => { - let url = `/api/${org_identifier}/${index}/_around?key=${key}&size=${size}&sql=${query_context}`; + let url = `/api/${org_identifier}/${index}/_around?key=${key}&size=${size}&sql=${query_context}&type=${stream_type}`; if (query_fn.trim() != "") { url = url + `&query_fn=${query_fn}`; }