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:
dal 2025-01-08 15:42:28 -07:00
parent b22ab09673
commit 533ef5a4ef
3 changed files with 129 additions and 8 deletions

View File

@ -1,25 +1,41 @@
use anyhow::Result;
use axum::{extract::Path, Extension, Json};
use axum::http::StatusCode;
use axum::{extract::Path, Extension, Json};
use diesel::prelude::*;
use diesel_async::RunQueryDsl;
use serde::Serialize;
use uuid::Uuid;
use crate::database::{
enums::{UserOrganizationRole, UserOrganizationStatus},
lib::get_pg_pool,
models::User,
schema::dataset_permissions,
models::{User, UserToOrganization},
schema::{
dataset_permissions, datasets_to_permission_groups, permission_groups_to_users, users,
users_to_organizations,
},
};
use crate::routes::rest::ApiResponse;
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)]
pub struct DatasetOverview {
pub dataset_id: Uuid,
pub total_permission_groups: i64,
pub total_dataset_groups: i64,
pub total_users: i64,
pub user_permission_lineages: Vec<UserPermissionLineage>,
}
pub async fn get_dataset_overview(
@ -32,7 +48,10 @@ pub async fn get_dataset_overview(
Ok(false) => return Err((StatusCode::FORBIDDEN, "Insufficient permissions")),
Err(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")
})?;
// 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
let permission_groups_count = dataset_permissions::table
.filter(
@ -50,7 +166,7 @@ pub async fn get_dataset_overview(
.and(dataset_permissions::deleted_at.is_null()),
)
.count()
.get_result::<i64>(&mut *conn)
.get_result::<i64>(&mut conn)
.await
.map_err(|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()),
)
.count()
.get_result::<i64>(&mut *conn)
.get_result::<i64>(&mut conn)
.await
.map_err(|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()),
)
.count()
.get_result::<i64>(&mut *conn)
.get_result::<i64>(&mut conn)
.await
.map_err(|e| {
tracing::error!("Error counting users: {:?}", e);
@ -92,6 +208,7 @@ pub async fn get_dataset_overview(
total_permission_groups: permission_groups_count,
total_dataset_groups: dataset_groups_count,
total_users: users_count,
user_permission_lineages,
};
Ok(ApiResponse::JsonData(overview))

View File

@ -25,6 +25,8 @@ pub struct AssetWithAssignment {
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(
Extension(user): Extension<User>,
Path((dataset_id, permission_type)): Path<(Uuid, String)>,

View File

@ -23,6 +23,8 @@ pub struct AssetAssignment {
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(
Extension(user): Extension<User>,
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()))
.execute(&mut *conn)
.await?;
tracing::debug!("Unassigned {} rows", rows_affected);
}
Ok::<_, anyhow::Error>(())