Files
openobserve/src/handler/http/request/authz/fga.rs
T

1344 lines
46 KiB
Rust

// Copyright 2025 OpenObserve Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use std::io::Error;
use actix_web::{HttpRequest, HttpResponse, delete, get, post, put, web};
#[cfg(feature = "enterprise")]
use o2_dex::meta::auth::RoleRequest;
use crate::common::meta::{
http::HttpResponse as MetaHttpResponse,
user::{UserGroup, UserGroupRequest, UserRoleRequest},
};
#[cfg(feature = "enterprise")]
/// CreateRoles
///
/// #{"ratelimit_module":"Roles", "ratelimit_module_operation":"create"}#
#[utoipa::path(
context_path = "/api",
tag = "Roles",
operation_id = "CreateRoles",
security(
("Authorization"= [])
),
params(
("org_id" = String, Path, description = "Organization name"),
),
request_body(content = UserRoleRequest, description = "UserRoleRequest", content_type = "application/json"),
responses(
(status = 200, description = "Success", content_type = "application/json", body = HttpResponse),
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
)
)]
#[post("/{org_id}/roles")]
pub async fn create_role(
org_id: web::Path<String>,
user_req: web::Json<UserRoleRequest>,
) -> Result<HttpResponse, Error> {
use crate::{
common::meta::user::is_standard_role, handler::http::auth::jwt::format_role_name_only,
};
let org_id = org_id.into_inner();
let user_req = user_req.into_inner();
let role_name = format_role_name_only(user_req.role.trim());
if role_name.is_empty() || is_standard_role(&role_name) {
return Ok(MetaHttpResponse::bad_request(
"Custom role name cannot be empty or standard role",
));
}
match o2_openfga::authorizer::roles::create_role(&role_name, &org_id).await {
Ok(_) => Ok(MetaHttpResponse::ok("Role created successfully")),
Err(err) => {
let err = err.to_string();
if err.contains("write_failed_due_to_invalid_input") {
Ok(MetaHttpResponse::bad_request("Role already exists"))
} else {
Ok(MetaHttpResponse::internal_error("Something went wrong"))
}
}
}
}
#[cfg(not(feature = "enterprise"))]
#[utoipa::path(
context_path = "/api",
tag = "Roles",
operation_id = "CreateRoles",
security(
("Authorization"= [])
),
params(
("org_id" = String, Path, description = "Organization name"),
),
request_body(content = UserRoleRequest, description = "UserRoleRequest", content_type = "application/json"),
responses(
(status = 200, description = "Success", content_type = "application/json", body = HttpResponse),
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
)
)]
#[post("/{org_id}/roles")]
pub async fn create_role(
_org_id: web::Path<String>,
_role_id: web::Json<UserRoleRequest>,
) -> Result<HttpResponse, Error> {
Ok(MetaHttpResponse::forbidden("Not Supported"))
}
#[cfg(feature = "enterprise")]
/// DeleteRole
///
/// #{"ratelimit_module":"Roles", "ratelimit_module_operation":"delete"}#
#[utoipa::path(
context_path = "/api",
tag = "Roles",
operation_id = "DeleteRole",
security(
("Authorization"= [])
),
params(
("org_id" = String, Path, description = "Organization name"),
("role_id" = String, Path, description = "Role Id"),
),
responses(
(status = 200, description = "Success", content_type = "application/json", body = HttpResponse),
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
)
)]
#[delete("/{org_id}/roles/{role_id}")]
pub async fn delete_role(path: web::Path<(String, String)>) -> Result<HttpResponse, Error> {
let (org_id, role_name) = path.into_inner();
match o2_openfga::authorizer::roles::delete_role(&org_id, &role_name).await {
Ok(_) => Ok(MetaHttpResponse::ok(
serde_json::json!({"successful": "true"}),
)),
Err(err) => Ok(MetaHttpResponse::internal_error(err)),
}
}
#[cfg(not(feature = "enterprise"))]
#[utoipa::path(
context_path = "/api",
tag = "Roles",
operation_id = "DeleteRole",
security(
("Authorization"= [])
),
params(
("org_id" = String, Path, description = "Organization name"),
("role_id" = String, Path, description = "Role Id"),
),
responses(
(status = 200, description = "Success", content_type = "application/json", body = HttpResponse),
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
)
)]
#[delete("/{org_id}/roles/{role_id}")]
pub async fn delete_role(_path: web::Path<(String, String)>) -> Result<HttpResponse, Error> {
Ok(MetaHttpResponse::forbidden("Not Supported"))
}
#[cfg(feature = "enterprise")]
/// ListRoles
///
/// #{"ratelimit_module":"Roles", "ratelimit_module_operation":"list"}#
#[utoipa::path(
context_path = "/api",
tag = "Roles",
operation_id = "ListRoles",
security(
("Authorization"= [])
),
params(
("org_id" = String, Path, description = "Organization name"),
),
responses(
(status = 200, description = "Success", content_type = "application/json", body = Vec<String>),
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
)
)]
#[get("/{org_id}/roles")]
pub async fn get_roles(org_id: web::Path<String>, req: HttpRequest) -> Result<HttpResponse, Error> {
let org_id = org_id.into_inner();
let mut permitted;
// Get List of allowed objects
let user_id = req.headers().get("user_id").unwrap();
match crate::handler::http::auth::validator::list_objects_for_user(
&org_id,
user_id.to_str().unwrap(),
"GET",
"role",
)
.await
{
Ok(list) => {
permitted = list;
}
Err(e) => {
return Ok(MetaHttpResponse::forbidden(e.to_string()));
}
}
// Get List of allowed objects ends
if let Some(mut local_permitted) = permitted {
let prefix = "role:";
for value in local_permitted.iter_mut() {
if value.starts_with(prefix) {
*value = value.strip_prefix(prefix).unwrap().to_string();
}
let role_prefix = format!("{org_id}/");
if value.starts_with(&role_prefix) {
*value = value.strip_prefix(&role_prefix).unwrap().to_string()
}
}
permitted = Some(local_permitted);
}
match o2_openfga::authorizer::roles::get_all_roles(&org_id, permitted).await {
Ok(res) => Ok(HttpResponse::Ok().json(res)),
Err(err) => Ok(MetaHttpResponse::internal_error(err)),
}
}
#[cfg(not(feature = "enterprise"))]
#[utoipa::path(
context_path = "/api",
tag = "Roles",
operation_id = "ListRoles",
security(
("Authorization"= [])
),
params(
("org_id" = String, Path, description = "Organization name"),
),
responses(
(status = 200, description = "Success", content_type = "application/json", body = Vec<String>),
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
)
)]
#[get("/{org_id}/roles")]
pub async fn get_roles(
_org_id: web::Path<String>,
_req: HttpRequest,
) -> Result<HttpResponse, Error> {
Ok(MetaHttpResponse::forbidden("Not Supported"))
}
#[cfg(feature = "enterprise")]
/// UpdateRoles
///
/// #{"ratelimit_module":"Roles", "ratelimit_module_operation":"update"}#
#[utoipa::path(
context_path = "/api",
tag = "Roles",
operation_id = "UpdateRoles",
security(
("Authorization"= [])
),
params(
("org_id" = String, Path, description = "Organization name"),
("role_id" = String, Path, description = "Role Id"),
),
request_body(content = RoleRequest, description = "RoleRequest", content_type = "application/json"),
responses(
(status = 200, description = "Success", content_type = "application/json", body = HttpResponse),
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
)
)]
#[put("/{org_id}/roles/{role_id}")]
pub async fn update_role(
path: web::Path<(String, String)>,
update_role: web::Json<RoleRequest>,
) -> Result<HttpResponse, Error> {
let (org_id, role_id) = path.into_inner();
let update_role = update_role.into_inner();
match o2_openfga::authorizer::roles::update_role(
&org_id,
&role_id,
update_role.add,
update_role.remove,
update_role.add_users,
update_role.remove_users,
)
.await
{
Ok(_) => Ok(MetaHttpResponse::ok("Role updated successfully")),
Err(err) => Ok(MetaHttpResponse::internal_error(err)),
}
}
#[cfg(not(feature = "enterprise"))]
#[utoipa::path(
context_path = "/api",
tag = "Roles",
operation_id = "UpdateRoles",
security(
("Authorization"= [])
),
params(
("org_id" = String, Path, description = "Organization name"),
("role_id" = String, Path, description = "Role Id"),
),
request_body(content = RoleRequest, description = "RoleRequest", content_type = "application/json"),
responses(
(status = 200, description = "Success", content_type = "application/json", body = HttpResponse),
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
)
)]
#[post("/{org_id}/roles/{role_id}")]
pub async fn update_role(
_path: web::Path<(String, String)>,
_permissions: web::Json<String>,
) -> Result<HttpResponse, actix_web::Error> {
Ok(MetaHttpResponse::forbidden("Not Supported"))
}
#[cfg(feature = "enterprise")]
/// GetResourcePermission
///
/// #{"ratelimit_module":"Roles", "ratelimit_module_operation":"get"}#
#[utoipa::path(
context_path = "/api",
tag = "Roles",
operation_id = "GetResourcePermission",
security(
("Authorization"= [])
),
params(
("org_id" = String, Path, description = "Organization name"),
("role_id" = String, Path, description = "Role Id"),
("resource" = String, Path, description = "resource"),
),
responses(
(status = 200, description = "Success", content_type = "application/json", body = Vec<O2EntityAuthorization>),
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
)
)]
#[get("/{org_id}/roles/{role_id}/permissions/{resource}")]
pub async fn get_role_permissions(
path: web::Path<(String, String, String)>,
) -> Result<HttpResponse, Error> {
let (org_id, role_id, resource) = path.into_inner();
match o2_openfga::authorizer::roles::get_role_permissions(&org_id, &role_id, &resource).await {
Ok(res) => Ok(HttpResponse::Ok().json(res)),
Err(err) => Ok(MetaHttpResponse::internal_error(err)),
}
}
#[cfg(not(feature = "enterprise"))]
#[utoipa::path(
context_path = "/api",
tag = "Roles",
operation_id = "GetResourcePermission",
security(
("Authorization"= [])
),
params(
("org_id" = String, Path, description = "Organization name"),
("role_id" = String, Path, description = "Role Id"),
("resource" = String, Path, description = "resource"),
),
responses(
(status = 200, description = "Success", content_type = "application/json", body = Vec<O2EntityAuthorization>),
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
)
)]
#[get("/{org_id}/roles/{role_id}/permissions/{resource}")]
pub async fn get_role_permissions(
_path: web::Path<(String, String, String)>,
) -> Result<HttpResponse, Error> {
Ok(MetaHttpResponse::forbidden("Not Supported"))
}
#[cfg(feature = "enterprise")]
/// GetRoleUsers
///
/// #{"ratelimit_module":"Roles", "ratelimit_module_operation":"list"}#
#[utoipa::path(
context_path = "/api",
tag = "Roles",
operation_id = "GetRoleUsers",
security(
("Authorization"= [])
),
params(
("org_id" = String, Path, description = "Organization name"),
("role_id" = String, Path, description = "Role Id"),
),
responses(
(status = 200, description = "Success", content_type = "application/json", body = Vec<String>),
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
)
)]
#[get("/{org_id}/roles/{role_id}/users")]
pub async fn get_users_with_role(path: web::Path<(String, String)>) -> Result<HttpResponse, Error> {
let (org_id, role_id) = path.into_inner();
match o2_openfga::authorizer::roles::get_users_with_role(&org_id, &role_id).await {
Ok(res) => Ok(HttpResponse::Ok().json(res)),
Err(err) => Ok(MetaHttpResponse::internal_error(err)),
}
}
#[cfg(not(feature = "enterprise"))]
#[utoipa::path(
context_path = "/api",
tag = "Roles",
operation_id = "GetRoLesUsers",
security(
("Authorization"= [])
),
params(
("org_id" = String, Path, description = "Organization name"),
("role_id" = String, Path, description = "Role Id"),
),
responses(
(status = 200, description = "Success", content_type = "application/json", body = Vec<String>),
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
)
)]
#[get("/{org_id}/roles/{role_id}/users")]
pub async fn get_users_with_role(_path: web::Path<(String, String)>) -> Result<HttpResponse, Error> {
Ok(MetaHttpResponse::forbidden("Not Supported"))
}
#[cfg(feature = "enterprise")]
#[get("/{org_id}/users/{user_email}/roles")]
pub async fn get_roles_for_user(path: web::Path<(String, String)>) -> Result<HttpResponse, Error> {
let (org_id, user_email) = path.into_inner();
let res = o2_openfga::authorizer::roles::get_roles_for_org_user(&org_id, &user_email).await;
Ok(HttpResponse::Ok().json(res))
}
#[cfg(not(feature = "enterprise"))]
#[get("/{org_id}/users/{user_email}/roles")]
pub async fn get_roles_for_user(_path: web::Path<(String, String)>) -> Result<HttpResponse, Error> {
Ok(MetaHttpResponse::forbidden("Not Supported"))
}
#[cfg(feature = "enterprise")]
#[get("/{org_id}/users/{user_email}/groups")]
pub async fn get_groups_for_user(path: web::Path<(String, String)>) -> Result<HttpResponse, Error> {
let (org_id, user_email) = path.into_inner();
match o2_openfga::authorizer::groups::get_groups_for_org_user(&org_id, &user_email).await {
Ok(res) => Ok(HttpResponse::Ok().json(res)),
Err(err) => Ok(MetaHttpResponse::internal_error(err)),
}
}
#[cfg(not(feature = "enterprise"))]
#[get("/{org_id}/users/{user_email}/groups")]
pub async fn get_groups_for_user(
_path: web::Path<(String, String)>,
) -> Result<HttpResponse, Error> {
Ok(MetaHttpResponse::forbidden("Not Supported"))
}
#[cfg(feature = "enterprise")]
/// CreateGroup
///
/// #{"ratelimit_module":"Groups", "ratelimit_module_operation":"create"}#
#[utoipa::path(
context_path = "/api",
tag = "Groups",
operation_id = "CreateGroup",
security(
("Authorization"= [])
),
params(
("org_id" = String, Path, description = "Organization name"),
),
request_body(content = UserGroup, description = "UserGroup", content_type = "application/json"),
responses(
(status = 200, description = "Success", content_type = "application/json", body = HttpResponse),
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
)
)]
#[post("/{org_id}/groups")]
pub async fn create_group(
org_id: web::Path<String>,
user_group: web::Json<UserGroup>,
) -> Result<HttpResponse, Error> {
use crate::handler::http::auth::jwt::format_role_name_only;
let org_id = org_id.into_inner();
let mut user_grp = user_group.into_inner();
user_grp.name = format_role_name_only(user_grp.name.trim());
match o2_openfga::authorizer::groups::create_group(
&org_id,
&user_grp.name,
user_grp.users.unwrap_or_default(),
)
.await
{
Ok(_) => Ok(MetaHttpResponse::ok("Group created successfully")),
Err(err) => Ok(MetaHttpResponse::internal_error(err)),
}
}
#[cfg(not(feature = "enterprise"))]
#[utoipa::path(
context_path = "/api",
tag = "Groups",
operation_id = "CreateGroup",
security(
("Authorization"= [])
),
params(
("org_id" = String, Path, description = "Organization name"),
),
request_body(content = UserGroup, description = "UserGroup", content_type = "application/json"),
responses(
(status = 200, description = "Success", content_type = "application/json", body = HttpResponse),
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
)
)]
#[post("/{org_id}/groups")]
pub async fn create_group(
_org_id: web::Path<String>,
_user_group: web::Json<UserGroup>,
) -> Result<HttpResponse, Error> {
Ok(MetaHttpResponse::forbidden("Not Supported"))
}
#[cfg(feature = "enterprise")]
/// UpdateGroup
///
/// #{"ratelimit_module":"Groups", "ratelimit_module_operation":"update"}#
#[utoipa::path(
context_path = "/api",
tag = "Groups",
operation_id = "UpdateGroup",
security(
("Authorization"= [])
),
params(
("org_id" = String, Path, description = "Organization name"),
("group_name" = String, Path, description = "Group name"),
),
request_body(content = UserGroupRequest, description = "UserGroupRequest", content_type = "application/json"),
responses(
(status = 200, description = "Success", content_type = "application/json", body = HttpResponse),
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
)
)]
#[put("/{org_id}/groups/{group_name}")]
pub async fn update_group(
path: web::Path<(String, String)>,
user_group: web::Json<UserGroupRequest>,
) -> Result<HttpResponse, Error> {
let (org_id, group_name) = path.into_inner();
let user_grp = user_group.into_inner();
match o2_openfga::authorizer::groups::update_group(
&org_id,
&group_name,
user_grp.add_users,
user_grp.remove_users,
user_grp.add_roles,
user_grp.remove_roles,
)
.await
{
Ok(_) => Ok(MetaHttpResponse::ok("Group updated successfully")),
Err(err) => Ok(MetaHttpResponse::internal_error(err)),
}
}
#[cfg(not(feature = "enterprise"))]
#[utoipa::path(
context_path = "/api",
tag = "Groups",
operation_id = "UpdateGroup",
security(
("Authorization"= [])
),
params(
("org_id" = String, Path, description = "Organization name"),
("group_name" = String, Path, description = "Group name"),
),
request_body(content = UserGroupRequest, description = "UserGroupRequest", content_type = "application/json"),
responses(
(status = 200, description = "Success", content_type = "application/json", body = HttpResponse),
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
)
)]
#[put("/{org_id}/groups/{group_name}")]
pub async fn update_group(
_path: web::Path<(String, String)>,
_user_group: web::Json<UserGroupRequest>,
) -> Result<HttpResponse, Error> {
Ok(MetaHttpResponse::forbidden("Not Supported"))
}
#[cfg(feature = "enterprise")]
/// ListGroups
///
/// #{"ratelimit_module":"Groups", "ratelimit_module_operation":"list"}#
#[utoipa::path(
context_path = "/api",
tag = "Groups",
operation_id = "ListGroups",
security(
("Authorization"= [])
),
params(
("org_id" = String, Path, description = "Organization name"),
),
responses(
(status = 200, description = "Success", content_type = "application/json", body = Vec<String>),
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
)
)]
#[get("/{org_id}/groups")]
pub async fn get_groups(path: web::Path<String>, req: HttpRequest) -> Result<HttpResponse, Error> {
let org_id = path.into_inner();
let mut permitted;
// Get List of allowed objects
let user_id = req.headers().get("user_id").unwrap();
match crate::handler::http::auth::validator::list_objects_for_user(
&org_id,
user_id.to_str().unwrap(),
"GET",
"group",
)
.await
{
Ok(list) => {
permitted = list;
}
Err(e) => {
return Ok(crate::common::meta::http::HttpResponse::forbidden(
e.to_string(),
));
}
}
// Get List of allowed objects ends
if let Some(mut local_permitted) = permitted {
let prefix = "group:";
for value in local_permitted.iter_mut() {
if value.starts_with(prefix) {
*value = value.strip_prefix(prefix).unwrap().to_string();
}
let group_prefix = format!("{org_id}/");
if value.starts_with(&group_prefix) {
*value = value.strip_prefix(&group_prefix).unwrap().to_string()
}
}
permitted = Some(local_permitted);
}
match o2_openfga::authorizer::groups::get_all_groups(&org_id, permitted).await {
Ok(res) => Ok(HttpResponse::Ok().json(res)),
Err(err) => Ok(MetaHttpResponse::internal_error(err)),
}
}
#[cfg(not(feature = "enterprise"))]
#[utoipa::path(
context_path = "/api",
tag = "Groups",
operation_id = "ListGroups",
security(
("Authorization"= [])
),
params(
("org_id" = String, Path, description = "Organization name"),
),
responses(
(status = 200, description = "Success", content_type = "application/json", body = Vec<String>),
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
)
)]
#[get("/{org_id}/groups")]
pub async fn get_groups(_path: web::Path<String>) -> Result<HttpResponse, Error> {
Ok(MetaHttpResponse::forbidden("Not Supported"))
}
#[cfg(feature = "enterprise")]
/// GetGroup
///
/// #{"ratelimit_module":"Groups", "ratelimit_module_operation":"get"}#
#[utoipa::path(
context_path = "/api",
tag = "Groups",
operation_id = "GetGroup",
security(
("Authorization"= [])
),
params(
("org_id" = String, Path, description = "Organization name"),
),
responses(
(status = 200, description = "Success", content_type = "application/json", body = UserGroup),
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
)
)]
#[get("/{org_id}/groups/{group_name}")]
pub async fn get_group_details(path: web::Path<(String, String)>) -> Result<HttpResponse, Error> {
let (org_id, group_name) = path.into_inner();
match o2_openfga::authorizer::groups::get_group_details(&org_id, &group_name).await {
Ok(res) => Ok(HttpResponse::Ok().json(res)),
Err(err) => Ok(MetaHttpResponse::internal_error(err)),
}
}
#[cfg(not(feature = "enterprise"))]
#[utoipa::path(
context_path = "/api",
tag = "Groups",
operation_id = "GetGroup",
security(
("Authorization"= [])
),
params(
("org_id" = String, Path, description = "Organization name"),
),
responses(
(status = 200, description = "Success", content_type = "application/json", body = UserGroup),
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
)
)]
#[get("/{org_id}/groups/{group_name}")]
pub async fn get_group_details(_path: web::Path<(String, String)>) -> Result<HttpResponse, Error> {
Ok(MetaHttpResponse::forbidden("Not Supported"))
}
#[cfg(feature = "enterprise")]
#[get("/{org_id}/resources")]
pub async fn get_resources(_org_id: web::Path<String>) -> Result<HttpResponse, Error> {
#[cfg(feature = "cloud")]
use o2_openfga::meta::mapping::NON_CLOUD_RESOURCE_KEYS;
use o2_openfga::meta::mapping::Resource;
let resources = o2_openfga::meta::mapping::OFGA_MODELS
.values()
.collect::<Vec<&Resource>>();
#[cfg(feature = "cloud")]
let resources = resources
.into_iter()
.filter(|r| !NON_CLOUD_RESOURCE_KEYS.contains(&r.key))
.collect::<Vec<&Resource>>();
Ok(HttpResponse::Ok().json(resources))
}
#[cfg(not(feature = "enterprise"))]
#[get("/{org_id}/resources")]
pub async fn get_resources(_org_id: web::Path<String>) -> Result<HttpResponse, Error> {
Ok(MetaHttpResponse::forbidden("Not Supported"))
}
#[cfg(feature = "enterprise")]
/// DeleteGroup
///
/// #{"ratelimit_module":"Groups", "ratelimit_module_operation":"delete"}#
#[utoipa::path(
context_path = "/api",
tag = "Groups",
operation_id = "DeleteGroup",
security(
("Authorization"= [])
),
params(
("org_id" = String, Path, description = "Organization name"),
("group_name" = String, Path, description = "Group name"),
),
responses(
(status = 200, description = "Success", content_type = "application/json", body = HttpResponse),
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
)
)]
#[delete("/{org_id}/groups/{group_name}")]
pub async fn delete_group(path: web::Path<(String, String)>) -> Result<HttpResponse, Error> {
let (org_id, group_name) = path.into_inner();
match o2_openfga::authorizer::groups::delete_group(&org_id, &group_name).await {
Ok(_) => Ok(MetaHttpResponse::ok(
serde_json::json!({"successful": "true"}),
)),
Err(err) => Ok(MetaHttpResponse::internal_error(err)),
}
}
#[cfg(not(feature = "enterprise"))]
#[utoipa::path(
context_path = "/api",
tag = "Groups",
operation_id = "DeleteGroup",
security(
("Authorization"= [])
),
params(
("org_id" = String, Path, description = "Organization name"),
("group_name" = String, Path, description = "Group name"),
),
responses(
(status = 200, description = "Success", content_type = "application/json", body = HttpResponse),
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
)
)]
#[delete("/{org_id}/groups/{group_name}")]
pub async fn delete_group(_path: web::Path<(String, String)>) -> Result<HttpResponse, Error> {
Ok(MetaHttpResponse::forbidden("Not Supported"))
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use actix_web::{App, test};
use super::*;
use crate::common::meta::user::{UserGroup, UserGroupRequest, UserRoleRequest};
#[tokio::test]
async fn test_create_role_enterprise_success() {
#[cfg(feature = "enterprise")]
{
let app = test::init_service(App::new().service(create_role)).await;
let role_request = UserRoleRequest {
role: "custom_role".to_string(),
custom: None,
};
let req = test::TestRequest::post()
.uri("/test_org/roles")
.set_json(&role_request)
.to_request();
let resp = test::call_service(&app, req).await;
// Note: This will likely fail in test environment due to missing OFGA setup
// but we're testing the endpoint structure and request handling
assert!(resp.status().is_client_error() || resp.status().is_server_error());
}
#[cfg(not(feature = "enterprise"))]
{
let app = test::init_service(App::new().service(create_role)).await;
let role_request = UserRoleRequest {
role: "custom_role".to_string(),
custom: None,
};
let req = test::TestRequest::post()
.uri("/test_org/roles")
.set_json(&role_request)
.to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), 403); // Forbidden in non-enterprise
}
}
#[tokio::test]
async fn test_create_role_empty_name() {
#[cfg(feature = "enterprise")]
{
let app = test::init_service(App::new().service(create_role)).await;
let role_request = UserRoleRequest {
role: " ".to_string(), // Empty after trim
custom: None,
};
let req = test::TestRequest::post()
.uri("/test_org/roles")
.set_json(&role_request)
.to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), 400); // Bad request for empty role name
}
}
#[tokio::test]
async fn test_create_role_standard_role() {
#[cfg(feature = "enterprise")]
{
let app = test::init_service(App::new().service(create_role)).await;
let role_request = UserRoleRequest {
role: "admin".to_string(), // Standard role
custom: None,
};
let req = test::TestRequest::post()
.uri("/test_org/roles")
.set_json(&role_request)
.to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), 400); // Bad request for standard role
}
}
#[tokio::test]
async fn test_delete_role_enterprise() {
#[cfg(feature = "enterprise")]
{
let app = test::init_service(App::new().service(delete_role)).await;
let req = test::TestRequest::delete()
.uri("/test_org/roles/test_role")
.to_request();
let resp = test::call_service(&app, req).await;
// Will likely fail due to missing OFGA setup, but testing structure
assert!(resp.status().is_client_error() || resp.status().is_server_error());
}
#[cfg(not(feature = "enterprise"))]
{
let app = test::init_service(App::new().service(delete_role)).await;
let req = test::TestRequest::delete()
.uri("/test_org/roles/test_role")
.to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), 403); // Forbidden in non-enterprise
}
}
#[tokio::test]
async fn test_get_roles_enterprise() {
#[cfg(feature = "enterprise")]
{
let app = test::init_service(App::new().service(get_roles)).await;
let mut req = test::TestRequest::get().uri("/test_org/roles").to_request();
// Add user_id header that the function expects
req.headers_mut()
.insert("user_id", "test_user".parse().unwrap());
let resp = test::call_service(&app, req).await;
// Will likely fail due to missing OFGA setup, but testing structure
assert!(resp.status().is_client_error() || resp.status().is_server_error());
}
#[cfg(not(feature = "enterprise"))]
{
let app = test::init_service(App::new().service(get_roles)).await;
let req = test::TestRequest::get().uri("/test_org/roles").to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), 403); // Forbidden in non-enterprise
}
}
#[tokio::test]
async fn test_update_role_enterprise() {
#[cfg(feature = "enterprise")]
{
let app = test::init_service(App::new().service(update_role)).await;
let role_request = o2_dex::meta::auth::RoleRequest {
add: Some(vec!["permission1".to_string()]),
remove: Some(vec!["permission2".to_string()]),
add_users: Some(vec!["user1".to_string()]),
remove_users: Some(vec!["user2".to_string()]),
};
let req = test::TestRequest::put()
.uri("/test_org/roles/test_role")
.set_json(&role_request)
.to_request();
let resp = test::call_service(&app, req).await;
// Will likely fail due to missing OFGA setup, but testing structure
assert!(resp.status().is_client_error() || resp.status().is_server_error());
}
#[cfg(not(feature = "enterprise"))]
{
let app = test::init_service(App::new().service(update_role)).await;
let req = test::TestRequest::post()
.uri("/test_org/roles/test_role")
.set_json(&"test")
.to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), 403); // Forbidden in non-enterprise
}
}
#[tokio::test]
async fn test_get_role_permissions_enterprise() {
#[cfg(feature = "enterprise")]
{
let app = test::init_service(App::new().service(get_role_permissions)).await;
let req = test::TestRequest::get()
.uri("/test_org/roles/test_role/permissions/test_resource")
.to_request();
let resp = test::call_service(&app, req).await;
// Will likely fail due to missing OFGA setup, but testing structure
assert!(resp.status().is_client_error() || resp.status().is_server_error());
}
#[cfg(not(feature = "enterprise"))]
{
let app = test::init_service(App::new().service(get_role_permissions)).await;
let req = test::TestRequest::get()
.uri("/test_org/roles/test_role/permissions/test_resource")
.to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), 403); // Forbidden in non-enterprise
}
}
#[tokio::test]
async fn test_get_users_with_role_enterprise() {
#[cfg(feature = "enterprise")]
{
let app = test::init_service(App::new().service(get_users_with_role)).await;
let req = test::TestRequest::get()
.uri("/test_org/roles/test_role/users")
.to_request();
let resp = test::call_service(&app, req).await;
// Will likely fail due to missing OFGA setup, but testing structure
assert!(resp.status().is_client_error() || resp.status().is_server_error());
}
#[cfg(not(feature = "enterprise"))]
{
let app = test::init_service(App::new().service(get_users_with_role)).await;
let req = test::TestRequest::get()
.uri("/test_org/roles/test_role/users")
.to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), 403); // Forbidden in non-enterprise
}
}
#[tokio::test]
async fn test_get_roles_for_user_enterprise() {
#[cfg(feature = "enterprise")]
{
let app = test::init_service(App::new().service(get_roles_for_user)).await;
let req = test::TestRequest::get()
.uri("/test_org/users/test@example.com/roles")
.to_request();
let resp = test::call_service(&app, req).await;
// Will likely fail due to missing OFGA setup, but testing structure
assert!(resp.status().is_client_error() || resp.status().is_server_error());
}
#[cfg(not(feature = "enterprise"))]
{
let app = test::init_service(App::new().service(get_roles_for_user)).await;
let req = test::TestRequest::get()
.uri("/test_org/users/test@example.com/roles")
.to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), 403); // Forbidden in non-enterprise
}
}
#[tokio::test]
async fn test_get_groups_for_user_enterprise() {
#[cfg(feature = "enterprise")]
{
let app = test::init_service(App::new().service(get_groups_for_user)).await;
let req = test::TestRequest::get()
.uri("/test_org/users/test@example.com/groups")
.to_request();
let resp = test::call_service(&app, req).await;
// Will likely fail due to missing OFGA setup, but testing structure
assert!(resp.status().is_client_error() || resp.status().is_server_error());
}
#[cfg(not(feature = "enterprise"))]
{
let app = test::init_service(App::new().service(get_groups_for_user)).await;
let req = test::TestRequest::get()
.uri("/test_org/users/test@example.com/groups")
.to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), 403); // Forbidden in non-enterprise
}
}
#[tokio::test]
async fn test_create_group_enterprise() {
#[cfg(feature = "enterprise")]
{
let app = test::init_service(App::new().service(create_group)).await;
let mut users = HashSet::new();
users.insert("user1".to_string());
users.insert("user2".to_string());
let group = UserGroup {
name: "test_group".to_string(),
users: Some(users),
roles: None,
};
let req = test::TestRequest::post()
.uri("/test_org/groups")
.set_json(&group)
.to_request();
let resp = test::call_service(&app, req).await;
// Will likely fail due to missing OFGA setup, but testing structure
assert!(resp.status().is_client_error() || resp.status().is_server_error());
}
#[cfg(not(feature = "enterprise"))]
{
let app = test::init_service(App::new().service(create_group)).await;
let group = UserGroup {
name: "test_group".to_string(),
users: None,
roles: None,
};
let req = test::TestRequest::post()
.uri("/test_org/groups")
.set_json(&group)
.to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), 403); // Forbidden in non-enterprise
}
}
#[tokio::test]
async fn test_update_group_enterprise() {
#[cfg(feature = "enterprise")]
{
let app = test::init_service(App::new().service(update_group)).await;
let mut add_users = HashSet::new();
add_users.insert("user1".to_string());
let group_request = UserGroupRequest {
add_users: Some(add_users),
remove_users: None,
add_roles: None,
remove_roles: None,
};
let req = test::TestRequest::put()
.uri("/test_org/groups/test_group")
.set_json(&group_request)
.to_request();
let resp = test::call_service(&app, req).await;
// Will likely fail due to missing OFGA setup, but testing structure
assert!(resp.status().is_client_error() || resp.status().is_server_error());
}
#[cfg(not(feature = "enterprise"))]
{
let app = test::init_service(App::new().service(update_group)).await;
let group_request = UserGroupRequest {
add_users: None,
remove_users: None,
add_roles: None,
remove_roles: None,
};
let req = test::TestRequest::put()
.uri("/test_org/groups/test_group")
.set_json(&group_request)
.to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), 403); // Forbidden in non-enterprise
}
}
#[tokio::test]
async fn test_get_groups_enterprise() {
#[cfg(feature = "enterprise")]
{
let app = test::init_service(App::new().service(get_groups)).await;
let mut req = test::TestRequest::get()
.uri("/test_org/groups")
.to_request();
// Add user_id header that the function expects
req.headers_mut()
.insert("user_id", "test_user".parse().unwrap());
let resp = test::call_service(&app, req).await;
// Will likely fail due to missing OFGA setup, but testing structure
assert!(resp.status().is_client_error() || resp.status().is_server_error());
}
#[cfg(not(feature = "enterprise"))]
{
let app = test::init_service(App::new().service(get_groups)).await;
let req = test::TestRequest::get()
.uri("/test_org/groups")
.to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), 403); // Forbidden in non-enterprise
}
}
#[tokio::test]
async fn test_get_group_details_enterprise() {
#[cfg(feature = "enterprise")]
{
let app = test::init_service(App::new().service(get_group_details)).await;
let req = test::TestRequest::get()
.uri("/test_org/groups/test_group")
.to_request();
let resp = test::call_service(&app, req).await;
// Will likely fail due to missing OFGA setup, but testing structure
assert!(resp.status().is_client_error() || resp.status().is_server_error());
}
#[cfg(not(feature = "enterprise"))]
{
let app = test::init_service(App::new().service(get_group_details)).await;
let req = test::TestRequest::get()
.uri("/test_org/groups/test_group")
.to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), 403); // Forbidden in non-enterprise
}
}
#[tokio::test]
async fn test_delete_group_enterprise() {
#[cfg(feature = "enterprise")]
{
let app = test::init_service(App::new().service(delete_group)).await;
let req = test::TestRequest::delete()
.uri("/test_org/groups/test_group")
.to_request();
let resp = test::call_service(&app, req).await;
// Will likely fail due to missing OFGA setup, but testing structure
assert!(resp.status().is_client_error() || resp.status().is_server_error());
}
#[cfg(not(feature = "enterprise"))]
{
let app = test::init_service(App::new().service(delete_group)).await;
let req = test::TestRequest::delete()
.uri("/test_org/groups/test_group")
.to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), 403); // Forbidden in non-enterprise
}
}
#[tokio::test]
async fn test_get_resources_enterprise() {
#[cfg(feature = "enterprise")]
{
let app = test::init_service(App::new().service(get_resources)).await;
let req = test::TestRequest::get()
.uri("/test_org/resources")
.to_request();
let resp = test::call_service(&app, req).await;
// Should succeed as it doesn't require OFGA setup
assert!(resp.status().is_success());
}
#[cfg(not(feature = "enterprise"))]
{
let app = test::init_service(App::new().service(get_resources)).await;
let req = test::TestRequest::get()
.uri("/test_org/resources")
.to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), 403); // Forbidden in non-enterprise
}
}
#[tokio::test]
async fn test_user_role_request_validation() {
let valid_request = UserRoleRequest {
role: "custom_role".to_string(),
custom: Some(vec!["permission1".to_string(), "permission2".to_string()]),
};
assert_eq!(valid_request.role, "custom_role");
assert_eq!(
valid_request.custom,
Some(vec!["permission1".to_string(), "permission2".to_string()])
);
let simple_request = UserRoleRequest {
role: "simple_role".to_string(),
custom: None,
};
assert_eq!(simple_request.role, "simple_role");
assert!(simple_request.custom.is_none());
}
#[tokio::test]
async fn test_user_group_validation() {
let mut users = HashSet::new();
users.insert("user1".to_string());
users.insert("user2".to_string());
let mut roles = HashSet::new();
roles.insert("role1".to_string());
let group = UserGroup {
name: "test_group".to_string(),
users: Some(users.clone()),
roles: Some(roles.clone()),
};
assert_eq!(group.name, "test_group");
assert_eq!(group.users, Some(users));
assert_eq!(group.roles, Some(roles));
}
#[tokio::test]
async fn test_user_group_request_validation() {
let mut add_users = HashSet::new();
add_users.insert("user1".to_string());
let mut remove_users = HashSet::new();
remove_users.insert("user2".to_string());
let request = UserGroupRequest {
add_users: Some(add_users.clone()),
remove_users: Some(remove_users.clone()),
add_roles: None,
remove_roles: None,
};
assert_eq!(request.add_users, Some(add_users));
assert_eq!(request.remove_users, Some(remove_users));
assert!(request.add_roles.is_none());
assert!(request.remove_roles.is_none());
}
}