mirror of https://github.com/buster-so/buster.git
Enhance dataset overview API with user permission lineage and error handling improvements
- Introduced UserPermissionLineage struct to provide detailed user access information in the dataset overview response. - Updated get_dataset_overview function to include comprehensive checks for user permissions, dataset group access, and direct access. - Improved error handling for database queries related to user permissions and access checks. - Added TODO comments in list_dataset_assets and put_dataset_assets routes to address future dataset group integration. These changes enhance the API's capability to manage and report on user permissions effectively.
This commit is contained in:
parent
b22ab09673
commit
533ef5a4ef
|
@ -1,25 +1,41 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use axum::{extract::Path, Extension, Json};
|
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
|
use axum::{extract::Path, Extension, Json};
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::RunQueryDsl;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::database::{
|
use crate::database::{
|
||||||
|
enums::{UserOrganizationRole, UserOrganizationStatus},
|
||||||
lib::get_pg_pool,
|
lib::get_pg_pool,
|
||||||
models::User,
|
models::{User, UserToOrganization},
|
||||||
schema::dataset_permissions,
|
schema::{
|
||||||
|
dataset_permissions, datasets_to_permission_groups, permission_groups_to_users, users,
|
||||||
|
users_to_organizations,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use crate::routes::rest::ApiResponse;
|
use crate::routes::rest::ApiResponse;
|
||||||
use crate::utils::security::checks::is_user_workspace_admin_or_data_admin;
|
use crate::utils::security::checks::is_user_workspace_admin_or_data_admin;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct UserPermissionLineage {
|
||||||
|
pub user_id: Uuid,
|
||||||
|
pub email: String,
|
||||||
|
pub can_query: bool,
|
||||||
|
pub organization_role_access: bool,
|
||||||
|
pub permission_group_access: bool,
|
||||||
|
pub dataset_group_access: bool,
|
||||||
|
pub direct_access: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct DatasetOverview {
|
pub struct DatasetOverview {
|
||||||
pub dataset_id: Uuid,
|
pub dataset_id: Uuid,
|
||||||
pub total_permission_groups: i64,
|
pub total_permission_groups: i64,
|
||||||
pub total_dataset_groups: i64,
|
pub total_dataset_groups: i64,
|
||||||
pub total_users: i64,
|
pub total_users: i64,
|
||||||
|
pub user_permission_lineages: Vec<UserPermissionLineage>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_dataset_overview(
|
pub async fn get_dataset_overview(
|
||||||
|
@ -32,7 +48,10 @@ pub async fn get_dataset_overview(
|
||||||
Ok(false) => return Err((StatusCode::FORBIDDEN, "Insufficient permissions")),
|
Ok(false) => return Err((StatusCode::FORBIDDEN, "Insufficient permissions")),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!("Error checking user permissions: {:?}", e);
|
tracing::error!("Error checking user permissions: {:?}", e);
|
||||||
return Err((StatusCode::INTERNAL_SERVER_ERROR, "Error checking user permissions"));
|
return Err((
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
"Error checking user permissions",
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +60,103 @@ pub async fn get_dataset_overview(
|
||||||
(StatusCode::INTERNAL_SERVER_ERROR, "Database error")
|
(StatusCode::INTERNAL_SERVER_ERROR, "Database error")
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
// Get all active users in the organization
|
||||||
|
let all_users = users_to_organizations::table
|
||||||
|
.inner_join(users::table.on(users_to_organizations::user_id.eq(users::id)))
|
||||||
|
.filter(users_to_organizations::status.eq(UserOrganizationStatus::Active))
|
||||||
|
.filter(users_to_organizations::deleted_at.is_null())
|
||||||
|
.select((users::id, users::email, users_to_organizations::role))
|
||||||
|
.load::<(Uuid, String, UserOrganizationRole)>(&mut conn)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
tracing::error!("Error getting users: {:?}", e);
|
||||||
|
(StatusCode::INTERNAL_SERVER_ERROR, "Database error")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Get permission group access for all users
|
||||||
|
let permission_group_access = permission_groups_to_users::table
|
||||||
|
.inner_join(
|
||||||
|
datasets_to_permission_groups::table
|
||||||
|
.on(datasets_to_permission_groups::permission_group_id
|
||||||
|
.eq(permission_groups_to_users::permission_group_id)),
|
||||||
|
)
|
||||||
|
.filter(datasets_to_permission_groups::dataset_id.eq(dataset_id))
|
||||||
|
.filter(datasets_to_permission_groups::deleted_at.is_null())
|
||||||
|
.select(permission_groups_to_users::user_id)
|
||||||
|
.load::<Uuid>(&mut conn)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
tracing::error!("Error checking permission group access: {:?}", e);
|
||||||
|
(StatusCode::INTERNAL_SERVER_ERROR, "Database error")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Get dataset group access
|
||||||
|
let dataset_group_access = dataset_permissions::table
|
||||||
|
.filter(
|
||||||
|
dataset_permissions::dataset_id
|
||||||
|
.eq(dataset_id)
|
||||||
|
.and(dataset_permissions::permission_type.eq("dataset_group"))
|
||||||
|
.and(dataset_permissions::deleted_at.is_null()),
|
||||||
|
)
|
||||||
|
.select(dataset_permissions::permission_id)
|
||||||
|
.load::<Uuid>(&mut conn)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
tracing::error!("Error checking dataset group access: {:?}", e);
|
||||||
|
(StatusCode::INTERNAL_SERVER_ERROR, "Database error")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Get direct access users
|
||||||
|
let direct_access = dataset_permissions::table
|
||||||
|
.filter(
|
||||||
|
dataset_permissions::dataset_id
|
||||||
|
.eq(dataset_id)
|
||||||
|
.and(dataset_permissions::permission_type.eq("user"))
|
||||||
|
.and(dataset_permissions::deleted_at.is_null()),
|
||||||
|
)
|
||||||
|
.select(dataset_permissions::permission_id)
|
||||||
|
.load::<Uuid>(&mut conn)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
tracing::error!("Error checking direct access: {:?}", e);
|
||||||
|
(StatusCode::INTERNAL_SERVER_ERROR, "Database error")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Build user lineages
|
||||||
|
let user_permission_lineages = all_users
|
||||||
|
.into_iter()
|
||||||
|
.map(|(user_id, email, role)| {
|
||||||
|
let organization_role_access = matches!(
|
||||||
|
role,
|
||||||
|
UserOrganizationRole::WorkspaceAdmin
|
||||||
|
| UserOrganizationRole::DataAdmin
|
||||||
|
| UserOrganizationRole::Querier
|
||||||
|
);
|
||||||
|
|
||||||
|
let permission_group_access = permission_group_access.contains(&user_id);
|
||||||
|
let dataset_group_access = dataset_group_access.contains(&user_id);
|
||||||
|
let direct_access = direct_access.contains(&user_id);
|
||||||
|
|
||||||
|
let can_query = if role == UserOrganizationRole::Viewer {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
organization_role_access
|
||||||
|
|| (role == UserOrganizationRole::RestrictedQuerier
|
||||||
|
&& (permission_group_access || dataset_group_access || direct_access))
|
||||||
|
};
|
||||||
|
|
||||||
|
UserPermissionLineage {
|
||||||
|
user_id,
|
||||||
|
email,
|
||||||
|
can_query,
|
||||||
|
organization_role_access,
|
||||||
|
permission_group_access,
|
||||||
|
dataset_group_access,
|
||||||
|
direct_access,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
// Count active permissions for each type
|
// Count active permissions for each type
|
||||||
let permission_groups_count = dataset_permissions::table
|
let permission_groups_count = dataset_permissions::table
|
||||||
.filter(
|
.filter(
|
||||||
|
@ -50,7 +166,7 @@ pub async fn get_dataset_overview(
|
||||||
.and(dataset_permissions::deleted_at.is_null()),
|
.and(dataset_permissions::deleted_at.is_null()),
|
||||||
)
|
)
|
||||||
.count()
|
.count()
|
||||||
.get_result::<i64>(&mut *conn)
|
.get_result::<i64>(&mut conn)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
tracing::error!("Error counting permission groups: {:?}", e);
|
tracing::error!("Error counting permission groups: {:?}", e);
|
||||||
|
@ -65,7 +181,7 @@ pub async fn get_dataset_overview(
|
||||||
.and(dataset_permissions::deleted_at.is_null()),
|
.and(dataset_permissions::deleted_at.is_null()),
|
||||||
)
|
)
|
||||||
.count()
|
.count()
|
||||||
.get_result::<i64>(&mut *conn)
|
.get_result::<i64>(&mut conn)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
tracing::error!("Error counting dataset groups: {:?}", e);
|
tracing::error!("Error counting dataset groups: {:?}", e);
|
||||||
|
@ -80,7 +196,7 @@ pub async fn get_dataset_overview(
|
||||||
.and(dataset_permissions::deleted_at.is_null()),
|
.and(dataset_permissions::deleted_at.is_null()),
|
||||||
)
|
)
|
||||||
.count()
|
.count()
|
||||||
.get_result::<i64>(&mut *conn)
|
.get_result::<i64>(&mut conn)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
tracing::error!("Error counting users: {:?}", e);
|
tracing::error!("Error counting users: {:?}", e);
|
||||||
|
@ -92,6 +208,7 @@ pub async fn get_dataset_overview(
|
||||||
total_permission_groups: permission_groups_count,
|
total_permission_groups: permission_groups_count,
|
||||||
total_dataset_groups: dataset_groups_count,
|
total_dataset_groups: dataset_groups_count,
|
||||||
total_users: users_count,
|
total_users: users_count,
|
||||||
|
user_permission_lineages,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(ApiResponse::JsonData(overview))
|
Ok(ApiResponse::JsonData(overview))
|
||||||
|
|
|
@ -25,6 +25,8 @@ pub struct AssetWithAssignment {
|
||||||
pub assigned: bool,
|
pub assigned: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: When we introduce the dataset groups, this list should look for where they are included, not related to permissions.
|
||||||
|
|
||||||
pub async fn list_assets(
|
pub async fn list_assets(
|
||||||
Extension(user): Extension<User>,
|
Extension(user): Extension<User>,
|
||||||
Path((dataset_id, permission_type)): Path<(Uuid, String)>,
|
Path((dataset_id, permission_type)): Path<(Uuid, String)>,
|
||||||
|
|
|
@ -23,6 +23,8 @@ pub struct AssetAssignment {
|
||||||
pub assigned: bool,
|
pub assigned: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: When we introduce the dataset groups, this list should update the datasets_to_dataset_groups table, not related to permissions.
|
||||||
|
|
||||||
pub async fn put_permissions(
|
pub async fn put_permissions(
|
||||||
Extension(user): Extension<User>,
|
Extension(user): Extension<User>,
|
||||||
Path((dataset_id, permission_type)): Path<(Uuid, String)>,
|
Path((dataset_id, permission_type)): Path<(Uuid, String)>,
|
||||||
|
@ -78,7 +80,7 @@ pub async fn put_permissions_handler(
|
||||||
.set(dataset_permissions::deleted_at.eq(Utc::now()))
|
.set(dataset_permissions::deleted_at.eq(Utc::now()))
|
||||||
.execute(&mut *conn)
|
.execute(&mut *conn)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
tracing::debug!("Unassigned {} rows", rows_affected);
|
tracing::debug!("Unassigned {} rows", rows_affected);
|
||||||
}
|
}
|
||||||
Ok::<_, anyhow::Error>(())
|
Ok::<_, anyhow::Error>(())
|
||||||
|
|
Loading…
Reference in New Issue