Merge branch 'staging' into big-nate/bus-891-reorganize-and-redo-permissions-tabs

This commit is contained in:
Nate Kelley 2025-01-09 08:50:04 -07:00
commit b648951d6a
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
3 changed files with 189 additions and 43 deletions

View File

@ -1,25 +1,46 @@
use anyhow::Result;
use axum::{extract::Path, Extension, Json};
use axum::http::StatusCode;
use axum::{extract::Path, Extension};
use diesel::prelude::*;
use diesel_async::RunQueryDsl;
use serde::Serialize;
use uuid::Uuid;
use crate::database::enums::IdentityType;
use crate::database::schema::sql_types::IdentityTypeEnum;
use crate::database::schema::{datasets, permission_groups, permission_groups_to_identities};
use crate::database::{
enums::{UserOrganizationRole, UserOrganizationStatus},
lib::get_pg_pool,
models::User,
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::utils::security::checks::is_user_workspace_admin_or_data_admin;
#[derive(Debug, Serialize)]
pub struct UserPermissionLineage {
pub id: Option<Uuid>,
#[serde(rename = "type")]
pub type_: String,
pub name: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct UserOverviewItem {
pub id: Uuid,
pub name: String,
pub can_query: bool,
pub lineage: Vec<Vec<UserPermissionLineage>>,
}
#[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 users: Vec<UserOverviewItem>,
}
pub async fn get_dataset_overview(
@ -32,7 +53,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,58 +65,176 @@ pub async fn get_dataset_overview(
(StatusCode::INTERNAL_SERVER_ERROR, "Database error")
})?;
// Count active permissions for each type
let permission_groups_count = dataset_permissions::table
.filter(
dataset_permissions::dataset_id
.eq(dataset_id)
.and(dataset_permissions::permission_type.eq("permission_group"))
.and(dataset_permissions::deleted_at.is_null()),
)
.count()
.get_result::<i64>(&mut *conn)
// Get all active users in the organization
let 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 counting permission groups: {:?}", e);
tracing::error!("Error getting users: {:?}", e);
(StatusCode::INTERNAL_SERVER_ERROR, "Database error")
})?;
let dataset_groups_count = 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()),
)
.count()
.get_result::<i64>(&mut *conn)
let user_ids = users.iter().map(|(id, _, _)| *id).collect::<Vec<_>>();
let datasets_query: Vec<(Uuid, String, Uuid)> = dataset_permissions::table
.inner_join(datasets::table.on(dataset_permissions::dataset_id.eq(datasets::id)))
.filter(dataset_permissions::dataset_id.eq(dataset_id))
.filter(dataset_permissions::permission_type.eq("user"))
.filter(dataset_permissions::deleted_at.is_null())
.filter(dataset_permissions::permission_id.eq_any(&user_ids))
.select((
datasets::id,
datasets::name,
dataset_permissions::permission_id,
))
.load::<(Uuid, String, Uuid)>(&mut conn)
.await
.map_err(|e| {
tracing::error!("Error counting dataset groups: {:?}", e);
tracing::error!("Error getting datasets: {:?}", e);
(StatusCode::INTERNAL_SERVER_ERROR, "Database error")
})?;
let users_count = 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()),
let permission_groups_query: Vec<(Uuid, String, Uuid)> = permission_groups_to_identities::table
.inner_join(
permission_groups::table
.on(permission_groups_to_identities::permission_group_id.eq(permission_groups::id)),
)
.count()
.get_result::<i64>(&mut *conn)
.inner_join(
dataset_permissions::table.on(permission_groups_to_identities::permission_group_id
.eq(dataset_permissions::permission_id)
.and(dataset_permissions::permission_type.eq("permission_group"))),
)
.filter(permission_groups_to_identities::identity_id.eq_any(&user_ids))
.filter(permission_groups_to_identities::identity_type.eq(IdentityType::User))
.filter(dataset_permissions::deleted_at.is_null())
.filter(dataset_permissions::dataset_id.eq(dataset_id))
.filter(permission_groups::deleted_at.is_null())
.select((
permission_groups::id,
permission_groups::name,
permission_groups_to_identities::identity_id,
))
.load::<(Uuid, String, Uuid)>(&mut conn)
.await
.map_err(|e| {
tracing::error!("Error counting users: {:?}", e);
tracing::error!("Error getting permission groups: {:?}", e);
(StatusCode::INTERNAL_SERVER_ERROR, "Database error")
})?;
let overview = DatasetOverview {
dataset_id,
total_permission_groups: permission_groups_count,
total_dataset_groups: dataset_groups_count,
total_users: users_count,
};
println!("datasets_query: {:?}", datasets_query);
println!("permission_groups_query: {:?}", permission_groups_query);
// TODO: will need to add dataset groups here when we turn them on.
let users = users
.into_iter()
.map(|(id, email, role)| {
let can_query = match role {
UserOrganizationRole::WorkspaceAdmin => true,
UserOrganizationRole::DataAdmin => true,
UserOrganizationRole::Querier => true,
_ => false,
};
let mut lineage = vec![];
let mut org_lineage = vec![UserPermissionLineage {
id: Some(id),
type_: String::from("user"),
name: Some(String::from("Default Access")),
}];
match role {
UserOrganizationRole::WorkspaceAdmin => {
org_lineage.push(UserPermissionLineage {
id: Some(id),
type_: String::from("user"),
name: Some(String::from("Workspace Admin")),
});
}
UserOrganizationRole::DataAdmin => {
org_lineage.push(UserPermissionLineage {
id: Some(id),
type_: String::from("user"),
name: Some(String::from("Data Admin")),
});
}
UserOrganizationRole::Querier => {
org_lineage.push(UserPermissionLineage {
id: Some(id),
type_: String::from("user"),
name: Some(String::from("Querier")),
});
}
UserOrganizationRole::RestrictedQuerier => {
org_lineage.push(UserPermissionLineage {
id: Some(id),
type_: String::from("user"),
name: Some(String::from("Restricted Querier")),
});
}
UserOrganizationRole::Viewer => {
org_lineage.push(UserPermissionLineage {
id: Some(id),
type_: String::from("user"),
name: Some(String::from("Viewer")),
});
}
_ => (),
}
lineage.push(org_lineage);
// Add direct dataset access lineage
if let Some((dataset_id, dataset_name, _)) =
datasets_query.iter().find(|(_, _, user_id)| *user_id == id)
{
lineage.push(vec![
UserPermissionLineage {
id: None,
type_: String::from("datasets"),
name: Some(String::from("Datasets")),
},
UserPermissionLineage {
id: Some(*dataset_id),
type_: String::from("datasets"),
name: Some(dataset_name.clone()),
},
]);
}
// Add permission group lineage
if let Some((group_id, group_name, _)) = permission_groups_query
.iter()
.find(|(_, _, user_id)| *user_id == id)
{
lineage.push(vec![
UserPermissionLineage {
id: None,
type_: String::from("permissionGroups"),
name: Some(String::from("Permission Groups")),
},
UserPermissionLineage {
id: Some(*group_id),
type_: String::from("permissionGroups"),
name: Some(group_name.clone()),
},
]);
}
return UserOverviewItem {
id,
name: email,
can_query,
lineage,
};
})
.collect();
let overview = DatasetOverview { dataset_id, users };
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>(())