Merge branch 'dallin/bus-920-feature-finish-rest-of-permissions' of https://github.com/buster-so/buster into dallin/bus-920-feature-finish-rest-of-permissions

This commit is contained in:
Nate Kelley 2025-01-20 15:58:04 -07:00
commit 909a7edd95
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
8 changed files with 138 additions and 44 deletions

View File

@ -0,0 +1,6 @@
-- This file should undo anything in `up.sql`
DROP TRIGGER IF EXISTS update_dataset_groups_permissions_updated_at ON dataset_groups_permissions;
DROP INDEX IF EXISTS dataset_groups_permissions_organization_id_idx;
DROP INDEX IF EXISTS dataset_groups_permissions_permission_id_idx;
DROP INDEX IF EXISTS dataset_groups_permissions_dataset_group_id_idx;
DROP TABLE IF EXISTS dataset_groups_permissions;

View File

@ -0,0 +1,15 @@
-- Your SQL goes here
CREATE TABLE dataset_groups_permissions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
dataset_group_id UUID NOT NULL REFERENCES dataset_groups(id),
permission_id UUID NOT NULL,
permission_type VARCHAR NOT NULL,
organization_id UUID NOT NULL REFERENCES organizations(id),
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE
);
CREATE INDEX dataset_groups_permissions_dataset_group_id_idx ON dataset_groups_permissions(dataset_group_id);
CREATE INDEX dataset_groups_permissions_permission_id_idx ON dataset_groups_permissions(permission_id);
CREATE INDEX dataset_groups_permissions_organization_id_idx ON dataset_groups_permissions(organization_id);

View File

@ -6,6 +6,13 @@ use serde::{Deserialize, Serialize};
use serde_json::Value;
use uuid::Uuid;
allow_columns_to_appear_in_same_group_by_clause!(
dataset_groups::id,
dataset_groups::name,
dataset_permissions::id,
dataset_groups_permissions::id,
);
#[derive(Queryable, Insertable, Identifiable, Associations, Debug)]
#[diesel(belongs_to(User, foreign_key = owner_id))]
#[diesel(table_name = api_keys)]
@ -517,3 +524,15 @@ pub struct DatasetPermission {
pub updated_at: DateTime<Utc>,
pub deleted_at: Option<DateTime<Utc>>,
}
#[derive(Queryable, Insertable, Debug)]
#[diesel(table_name = dataset_groups_permissions)]
pub struct DatasetGroupPermission {
pub id: Uuid,
pub dataset_group_id: Uuid,
pub permission_id: Uuid,
pub permission_type: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub deleted_at: Option<DateTime<Utc>>,
}

View File

@ -201,6 +201,19 @@ diesel::table! {
}
}
diesel::table! {
dataset_groups_permissions (id) {
id -> Uuid,
dataset_group_id -> Uuid,
permission_id -> Uuid,
permission_type -> Varchar,
organization_id -> Uuid,
created_at -> Timestamptz,
updated_at -> Timestamptz,
deleted_at -> Nullable<Timestamptz>,
}
}
diesel::table! {
dataset_permissions (id) {
id -> Uuid,
@ -506,6 +519,8 @@ diesel::joinable!(dashboard_versions -> dashboards (dashboard_id));
diesel::joinable!(dashboards -> organizations (organization_id));
diesel::joinable!(data_sources -> organizations (organization_id));
diesel::joinable!(dataset_groups -> organizations (organization_id));
diesel::joinable!(dataset_groups_permissions -> dataset_groups (dataset_group_id));
diesel::joinable!(dataset_groups_permissions -> organizations (organization_id));
diesel::joinable!(dataset_permissions -> datasets (dataset_id));
diesel::joinable!(dataset_permissions -> organizations (organization_id));
diesel::joinable!(datasets -> data_sources (data_source_id));
@ -544,6 +559,7 @@ diesel::allow_tables_to_appear_in_same_query!(
data_sources,
dataset_columns,
dataset_groups,
dataset_groups_permissions,
dataset_permissions,
datasets,
datasets_to_dataset_groups,

View File

@ -56,7 +56,7 @@ async fn list_attributes_handler(user: User, user_id: Uuid) -> Result<Vec<Attrib
None => return Err(anyhow::anyhow!("User organization id not found")),
};
let auth_user_role = match user.attributes.get("role") {
let auth_user_role = match user.attributes.get("organization_role") {
Some(Value::String(role)) => role,
Some(_) => return Err(anyhow::anyhow!("User role not found")),
None => return Err(anyhow::anyhow!("User role not found")),
@ -86,10 +86,17 @@ async fn list_attributes_handler(user: User, user_id: Uuid) -> Result<Vec<Attrib
for (key, value) in user_attributes.as_object().unwrap() {
if let Some(value_str) = value.as_str() {
let read_only = [
"organization_id",
"organization_role",
"user_id",
"user_email",
]
.contains(&key.as_str());
attributes.push(AttributeInfo {
name: key.to_string(),
value: value_str.to_string(),
read_only: false,
read_only,
});
}
}

View File

@ -1,15 +1,15 @@
use anyhow::Result;
use axum::extract::Path;
use axum::http::StatusCode;
use axum::Extension;
use chrono::{DateTime, Utc};
use diesel::prelude::*;
use diesel_async::RunQueryDsl;
use serde::Serialize;
use uuid::Uuid;
use crate::database::lib::get_pg_pool;
use crate::database::models::{DatasetGroup, User};
use crate::database::schema::dataset_groups;
use crate::database::models::User;
use crate::database::schema::{dataset_groups, dataset_groups_permissions, dataset_permissions};
use crate::routes::rest::ApiResponse;
use crate::utils::user::user_info::get_user_organization_id;
@ -17,15 +17,15 @@ use crate::utils::user::user_info::get_user_organization_id;
pub struct DatasetGroupInfo {
pub id: Uuid,
pub name: String,
pub organization_id: Uuid,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub permission_count: i64,
pub assigned: bool,
}
pub async fn list_dataset_groups(
Extension(user): Extension<User>,
Path(id): Path<Uuid>,
) -> Result<ApiResponse<Vec<DatasetGroupInfo>>, (StatusCode, &'static str)> {
let dataset_groups = match list_dataset_groups_handler(user).await {
let dataset_groups = match list_dataset_groups_handler(user, id).await {
Ok(groups) => groups,
Err(e) => {
tracing::error!("Error listing dataset groups: {:?}", e);
@ -39,25 +39,51 @@ pub async fn list_dataset_groups(
Ok(ApiResponse::JsonData(dataset_groups))
}
async fn list_dataset_groups_handler(user: User) -> Result<Vec<DatasetGroupInfo>> {
async fn list_dataset_groups_handler(user: User, id: Uuid) -> Result<Vec<DatasetGroupInfo>> {
let mut conn = get_pg_pool().get().await?;
let organization_id = get_user_organization_id(&user.id).await?;
let groups: Vec<DatasetGroup> = dataset_groups::table
let groups = dataset_groups::table
.left_join(
dataset_groups_permissions::table.on(dataset_groups_permissions::dataset_group_id
.eq(dataset_groups::id)
.and(dataset_groups_permissions::permission_type.eq("user"))
.and(dataset_groups_permissions::permission_id.eq(id))
.and(dataset_groups_permissions::deleted_at.is_null())),
)
.left_join(
dataset_permissions::table.on(dataset_permissions::permission_id
.eq(dataset_groups::id)
.and(dataset_permissions::permission_type.eq("dataset_group"))
.and(dataset_permissions::deleted_at.is_null())
.and(dataset_permissions::organization_id.eq(organization_id))),
)
.select((
dataset_groups::id,
dataset_groups::name,
diesel::dsl::sql::<diesel::sql_types::BigInt>(
"COALESCE(count(dataset_permissions.id), 0)",
),
diesel::dsl::sql::<diesel::sql_types::Bool>("dataset_groups_permissions.id IS NOT NULL"),
))
.group_by((
dataset_groups::id,
dataset_groups::name,
dataset_groups_permissions::id,
))
.filter(dataset_groups::organization_id.eq(organization_id))
.filter(dataset_groups::deleted_at.is_null())
.order_by(dataset_groups::created_at.desc())
.load(&mut *conn)
.load::<(Uuid, String, i64, bool)>(&mut *conn)
.await?;
Ok(groups
.into_iter()
.map(|group| DatasetGroupInfo {
id: group.id,
name: group.name,
organization_id: group.organization_id,
created_at: group.created_at,
updated_at: group.updated_at,
.map(|(id, name, permission_count, assigned)| DatasetGroupInfo {
id,
name,
permission_count,
assigned,
})
.collect())
}

View File

@ -1,15 +1,15 @@
use anyhow::Result;
use axum::extract::Path;
use axum::http::StatusCode;
use axum::Extension;
use chrono::{DateTime, Utc};
use diesel::prelude::*;
use diesel_async::RunQueryDsl;
use serde::Serialize;
use uuid::Uuid;
use crate::database::lib::get_pg_pool;
use crate::database::models::{Dataset, User};
use crate::database::schema::datasets;
use crate::database::models::User;
use crate::database::schema::{dataset_permissions, datasets};
use crate::routes::rest::ApiResponse;
use crate::utils::user::user_info::get_user_organization_id;
@ -17,18 +17,14 @@ use crate::utils::user::user_info::get_user_organization_id;
pub struct DatasetInfo {
pub id: Uuid,
pub name: String,
pub organization_id: Uuid,
pub data_source_id: Uuid,
pub enabled: bool,
pub imported: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub assigned: bool,
}
pub async fn list_datasets(
Extension(user): Extension<User>,
Path(id): Path<Uuid>,
) -> Result<ApiResponse<Vec<DatasetInfo>>, (StatusCode, &'static str)> {
let datasets = match list_datasets_handler(user).await {
let datasets = match list_datasets_handler(user, id).await {
Ok(datasets) => datasets,
Err(e) => {
tracing::error!("Error listing datasets: {:?}", e);
@ -39,28 +35,37 @@ pub async fn list_datasets(
Ok(ApiResponse::JsonData(datasets))
}
async fn list_datasets_handler(user: User) -> Result<Vec<DatasetInfo>> {
async fn list_datasets_handler(user: User, user_id: Uuid) -> Result<Vec<DatasetInfo>> {
let mut conn = get_pg_pool().get().await?;
let organization_id = get_user_organization_id(&user.id).await?;
let datasets: Vec<Dataset> = datasets::table
let datasets = match datasets::table
.left_join(
dataset_permissions::table.on(dataset_permissions::dataset_id
.eq(datasets::id)
.and(dataset_permissions::permission_type.eq("user"))
.and(dataset_permissions::permission_id.eq(user_id))
.and(dataset_permissions::deleted_at.is_null())),
)
.filter(datasets::organization_id.eq(organization_id))
.filter(datasets::deleted_at.is_null())
.order_by(datasets::created_at.desc())
.load(&mut *conn)
.await?;
.select((
datasets::id,
datasets::name,
diesel::dsl::sql::<diesel::sql_types::Bool>("dataset_permissions.id IS NOT NULL"),
))
.load::<(Uuid, String, bool)>(&mut *conn)
.await
{
Ok(datasets) => datasets,
Err(e) => {
tracing::error!("Error listing datasets: {:?}", e);
return Err(anyhow::anyhow!("Error listing datasets"));
}
};
Ok(datasets
.into_iter()
.map(|dataset| DatasetInfo {
id: dataset.id,
name: dataset.name,
organization_id: dataset.organization_id,
data_source_id: dataset.data_source_id,
enabled: dataset.enabled,
imported: dataset.imported,
created_at: dataset.created_at,
updated_at: dataset.updated_at,
})
.map(|(id, name, assigned)| DatasetInfo { id, name, assigned })
.collect())
}

View File

@ -25,7 +25,7 @@ pub async fn is_user_workspace_admin_or_data_admin(
None => return Err(anyhow::anyhow!("User organization id not found")),
};
let user_role = match user.attributes.get("role") {
let user_role = match user.attributes.get("organization_role") {
Some(Value::String(role)) => role,
Some(_) => return Err(anyhow::anyhow!("User role not found")),
None => return Err(anyhow::anyhow!("User role not found")),