2025-04-18 01:38:21 +08:00
|
|
|
//! Library for handling dataset security and permissions.
|
|
|
|
|
2025-04-03 01:31:43 +08:00
|
|
|
use anyhow::{anyhow, Result};
|
2025-04-19 00:36:57 +08:00
|
|
|
use chrono::{DateTime, Utc};
|
|
|
|
use database::enums::UserOrganizationRole;
|
|
|
|
use diesel::prelude::Queryable;
|
|
|
|
use diesel::{
|
|
|
|
BoolExpressionMethods, ExpressionMethods, JoinOnDsl, QueryDsl, Selectable, SelectableHelper,
|
|
|
|
};
|
2025-04-03 01:31:43 +08:00
|
|
|
use diesel_async::RunQueryDsl;
|
|
|
|
use uuid::Uuid;
|
|
|
|
|
|
|
|
use database::{
|
|
|
|
pool::{get_pg_pool, PgPool},
|
|
|
|
schema::{
|
2025-04-19 00:36:57 +08:00
|
|
|
datasets, datasets_to_permission_groups, permission_groups,
|
|
|
|
permission_groups_to_identities, teams_to_users, users_to_organizations,
|
2025-04-03 01:31:43 +08:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2025-04-19 00:36:57 +08:00
|
|
|
// Define the new struct mirroring the one in search_data_catalog.rs
|
|
|
|
#[derive(Queryable, Selectable, Clone, Debug)]
|
|
|
|
#[diesel(table_name = datasets)]
|
|
|
|
#[diesel(check_for_backend(diesel::pg::Pg))]
|
|
|
|
pub struct PermissionedDataset {
|
|
|
|
pub id: Uuid,
|
|
|
|
pub name: String,
|
|
|
|
#[diesel(column_name = "yml_file")]
|
|
|
|
pub yml_content: Option<String>, // Matches the local struct field name
|
|
|
|
#[allow(dead_code)]
|
|
|
|
pub created_at: DateTime<Utc>,
|
|
|
|
#[allow(dead_code)]
|
|
|
|
pub updated_at: DateTime<Utc>,
|
|
|
|
#[allow(dead_code)]
|
|
|
|
pub deleted_at: Option<DateTime<Utc>>,
|
|
|
|
}
|
|
|
|
|
2025-04-03 01:31:43 +08:00
|
|
|
pub async fn get_permissioned_datasets(
|
|
|
|
user_id: &Uuid,
|
|
|
|
page: i64,
|
|
|
|
page_size: i64,
|
2025-04-19 00:36:57 +08:00
|
|
|
) -> Result<Vec<PermissionedDataset>> {
|
|
|
|
let mut conn = match get_pg_pool().get().await {
|
2025-04-03 01:31:43 +08:00
|
|
|
Ok(conn) => conn,
|
|
|
|
Err(e) => return Err(anyhow!("Unable to get connection from pool: {}", e)),
|
|
|
|
};
|
|
|
|
|
2025-04-19 00:36:57 +08:00
|
|
|
// Fetch user's organization and role
|
|
|
|
let user_org_info = users_to_organizations::table
|
|
|
|
.filter(users_to_organizations::user_id.eq(user_id))
|
|
|
|
.select((
|
|
|
|
users_to_organizations::organization_id,
|
|
|
|
users_to_organizations::role,
|
|
|
|
))
|
|
|
|
.first::<(Uuid, UserOrganizationRole)>(&mut conn)
|
|
|
|
.await;
|
2025-04-18 01:38:21 +08:00
|
|
|
|
2025-04-19 00:36:57 +08:00
|
|
|
let datasets_query = match user_org_info {
|
|
|
|
Ok((organization_id, role)) => {
|
|
|
|
// Check if user has admin/querier role
|
|
|
|
if matches!(
|
|
|
|
role,
|
|
|
|
UserOrganizationRole::WorkspaceAdmin
|
|
|
|
| UserOrganizationRole::DataAdmin
|
|
|
|
| UserOrganizationRole::Querier
|
|
|
|
) {
|
|
|
|
// User is admin/querier, return all org datasets
|
|
|
|
datasets::table
|
|
|
|
.filter(datasets::organization_id.eq(organization_id))
|
|
|
|
.filter(datasets::deleted_at.is_null())
|
|
|
|
.select(PermissionedDataset::as_select())
|
|
|
|
.limit(page_size)
|
|
|
|
.offset(page * page_size)
|
|
|
|
.load::<PermissionedDataset>(&mut conn)
|
|
|
|
.await
|
|
|
|
} else {
|
|
|
|
// User is not admin/querier, use permission group logic
|
|
|
|
datasets::table
|
|
|
|
.select(PermissionedDataset::as_select())
|
|
|
|
.inner_join(
|
|
|
|
datasets_to_permission_groups::table
|
|
|
|
.on(datasets::id.eq(datasets_to_permission_groups::dataset_id)),
|
|
|
|
)
|
|
|
|
.inner_join(
|
|
|
|
permission_groups::table
|
|
|
|
.on(datasets_to_permission_groups::permission_group_id
|
|
|
|
.eq(permission_groups::id)),
|
|
|
|
)
|
|
|
|
.inner_join(
|
|
|
|
permission_groups_to_identities::table.on(permission_groups::id
|
|
|
|
.eq(permission_groups_to_identities::permission_group_id)),
|
|
|
|
)
|
|
|
|
.inner_join(teams_to_users::table.on(
|
|
|
|
teams_to_users::team_id.eq(permission_groups_to_identities::identity_id),
|
|
|
|
))
|
|
|
|
.filter(
|
|
|
|
teams_to_users::user_id
|
|
|
|
.eq(user_id)
|
|
|
|
.or(permission_groups_to_identities::identity_id.eq(user_id)),
|
|
|
|
)
|
|
|
|
.filter(datasets::deleted_at.is_null())
|
|
|
|
// Ensure related permission records are not deleted (important for non-admins)
|
|
|
|
.filter(
|
|
|
|
datasets_to_permission_groups::deleted_at
|
|
|
|
.is_null()
|
|
|
|
.and(permission_groups::deleted_at.is_null())
|
|
|
|
.and(permission_groups_to_identities::deleted_at.is_null())
|
|
|
|
.and(teams_to_users::deleted_at.is_null()),
|
|
|
|
)
|
|
|
|
.distinct() // Ensure unique datasets if multiple paths grant access
|
|
|
|
.limit(page_size)
|
|
|
|
.offset(page * page_size)
|
|
|
|
.load::<PermissionedDataset>(&mut conn)
|
|
|
|
.await
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(diesel::NotFound) => {
|
|
|
|
// User not found in any organization, return empty vec or error?
|
|
|
|
// Returning empty for now, indicating no datasets accessible.
|
|
|
|
Ok(Vec::new())
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
// Other database error fetching user role
|
|
|
|
return Err(anyhow!("Error fetching user organization role: {}", e));
|
|
|
|
}
|
2025-04-03 01:31:43 +08:00
|
|
|
};
|
|
|
|
|
2025-04-19 00:36:57 +08:00
|
|
|
match datasets_query {
|
|
|
|
Ok(datasets) => Ok(datasets),
|
|
|
|
Err(e) => Err(anyhow!("Unable to get datasets from database: {}", e)),
|
|
|
|
}
|
2025-04-03 01:31:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn has_dataset_access(user_id: &Uuid, dataset_id: &Uuid) -> Result<bool> {
|
|
|
|
let mut conn = match get_pg_pool().get().await {
|
|
|
|
Ok(conn) => conn,
|
|
|
|
Err(e) => return Err(anyhow!("Unable to get connection from pool: {}", e)),
|
|
|
|
};
|
|
|
|
|
2025-04-19 00:36:57 +08:00
|
|
|
// First, check if the user is an admin/querier for the dataset's organization
|
|
|
|
let admin_access = match datasets::table
|
|
|
|
.filter(datasets::id.eq(dataset_id))
|
|
|
|
.inner_join(
|
|
|
|
users_to_organizations::table
|
|
|
|
.on(datasets::organization_id.eq(users_to_organizations::organization_id)),
|
|
|
|
)
|
|
|
|
.filter(users_to_organizations::user_id.eq(user_id))
|
|
|
|
.select(users_to_organizations::role)
|
|
|
|
.first::<UserOrganizationRole>(&mut conn)
|
|
|
|
.await
|
|
|
|
{
|
|
|
|
Ok(role) => matches!(
|
|
|
|
role,
|
|
|
|
UserOrganizationRole::WorkspaceAdmin
|
|
|
|
| UserOrganizationRole::DataAdmin
|
|
|
|
| UserOrganizationRole::Querier
|
|
|
|
),
|
|
|
|
Err(diesel::NotFound) => false, // User not in the dataset's organization or dataset doesn't exist
|
|
|
|
Err(e) => return Err(anyhow!("Error checking admin access for dataset: {}", e)),
|
|
|
|
};
|
|
|
|
|
|
|
|
if admin_access {
|
|
|
|
return Ok(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
// If not admin, check permission group access (existing logic)
|
|
|
|
let group_access = match datasets::table
|
2025-04-03 01:31:43 +08:00
|
|
|
.select(datasets::id)
|
|
|
|
.inner_join(
|
|
|
|
datasets_to_permission_groups::table
|
|
|
|
.on(datasets::id.eq(datasets_to_permission_groups::dataset_id)),
|
|
|
|
)
|
|
|
|
.inner_join(
|
|
|
|
permission_groups::table
|
|
|
|
.on(datasets_to_permission_groups::permission_group_id.eq(permission_groups::id)),
|
|
|
|
)
|
|
|
|
.inner_join(
|
|
|
|
permission_groups_to_identities::table
|
|
|
|
.on(permission_groups::id.eq(permission_groups_to_identities::permission_group_id)),
|
|
|
|
)
|
|
|
|
.inner_join(
|
|
|
|
teams_to_users::table
|
|
|
|
.on(teams_to_users::team_id.eq(permission_groups_to_identities::identity_id)),
|
|
|
|
)
|
|
|
|
.filter(
|
|
|
|
teams_to_users::user_id
|
|
|
|
.eq(&user_id)
|
|
|
|
.or(permission_groups_to_identities::identity_id.eq(&user_id)),
|
|
|
|
)
|
|
|
|
.filter(datasets::id.eq(&dataset_id))
|
|
|
|
.filter(datasets::deleted_at.is_null())
|
|
|
|
.filter(
|
|
|
|
datasets_to_permission_groups::deleted_at
|
|
|
|
.is_null()
|
|
|
|
.and(permission_groups::deleted_at.is_null())
|
|
|
|
.and(permission_groups_to_identities::deleted_at.is_null())
|
|
|
|
.and(teams_to_users::deleted_at.is_null()),
|
|
|
|
)
|
|
|
|
.first::<Uuid>(&mut conn)
|
|
|
|
.await
|
|
|
|
{
|
|
|
|
Ok(_) => true,
|
|
|
|
Err(diesel::NotFound) => false,
|
|
|
|
Err(e) => return Err(anyhow!("Unable to get team datasets from database: {}", e)),
|
|
|
|
};
|
|
|
|
|
2025-04-19 00:36:57 +08:00
|
|
|
Ok(group_access)
|
|
|
|
}
|