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 serde_json::Value;
use uuid::Uuid; 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)] #[derive(Queryable, Insertable, Identifiable, Associations, Debug)]
#[diesel(belongs_to(User, foreign_key = owner_id))] #[diesel(belongs_to(User, foreign_key = owner_id))]
#[diesel(table_name = api_keys)] #[diesel(table_name = api_keys)]
@ -517,3 +524,15 @@ pub struct DatasetPermission {
pub updated_at: DateTime<Utc>, pub updated_at: DateTime<Utc>,
pub deleted_at: Option<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! { diesel::table! {
dataset_permissions (id) { dataset_permissions (id) {
id -> Uuid, id -> Uuid,
@ -506,6 +519,8 @@ diesel::joinable!(dashboard_versions -> dashboards (dashboard_id));
diesel::joinable!(dashboards -> organizations (organization_id)); diesel::joinable!(dashboards -> organizations (organization_id));
diesel::joinable!(data_sources -> organizations (organization_id)); diesel::joinable!(data_sources -> organizations (organization_id));
diesel::joinable!(dataset_groups -> 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 -> datasets (dataset_id));
diesel::joinable!(dataset_permissions -> organizations (organization_id)); diesel::joinable!(dataset_permissions -> organizations (organization_id));
diesel::joinable!(datasets -> data_sources (data_source_id)); diesel::joinable!(datasets -> data_sources (data_source_id));
@ -544,6 +559,7 @@ diesel::allow_tables_to_appear_in_same_query!(
data_sources, data_sources,
dataset_columns, dataset_columns,
dataset_groups, dataset_groups,
dataset_groups_permissions,
dataset_permissions, dataset_permissions,
datasets, datasets,
datasets_to_dataset_groups, 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")), 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(Value::String(role)) => role,
Some(_) => return Err(anyhow::anyhow!("User role not found")), Some(_) => return Err(anyhow::anyhow!("User role not found")),
None => 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() { for (key, value) in user_attributes.as_object().unwrap() {
if let Some(value_str) = value.as_str() { 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 { attributes.push(AttributeInfo {
name: key.to_string(), name: key.to_string(),
value: value_str.to_string(), value: value_str.to_string(),
read_only: false, read_only,
}); });
} }
} }

View File

@ -1,15 +1,15 @@
use anyhow::Result; use anyhow::Result;
use axum::extract::Path;
use axum::http::StatusCode; use axum::http::StatusCode;
use axum::Extension; use axum::Extension;
use chrono::{DateTime, Utc};
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::lib::get_pg_pool; use crate::database::lib::get_pg_pool;
use crate::database::models::{DatasetGroup, User}; use crate::database::models::User;
use crate::database::schema::dataset_groups; use crate::database::schema::{dataset_groups, dataset_groups_permissions, dataset_permissions};
use crate::routes::rest::ApiResponse; use crate::routes::rest::ApiResponse;
use crate::utils::user::user_info::get_user_organization_id; 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 struct DatasetGroupInfo {
pub id: Uuid, pub id: Uuid,
pub name: String, pub name: String,
pub organization_id: Uuid, pub permission_count: i64,
pub created_at: DateTime<Utc>, pub assigned: bool,
pub updated_at: DateTime<Utc>,
} }
pub async fn list_dataset_groups( pub async fn list_dataset_groups(
Extension(user): Extension<User>, Extension(user): Extension<User>,
Path(id): Path<Uuid>,
) -> Result<ApiResponse<Vec<DatasetGroupInfo>>, (StatusCode, &'static str)> { ) -> 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, Ok(groups) => groups,
Err(e) => { Err(e) => {
tracing::error!("Error listing dataset groups: {:?}", e); tracing::error!("Error listing dataset groups: {:?}", e);
@ -39,25 +39,51 @@ pub async fn list_dataset_groups(
Ok(ApiResponse::JsonData(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 mut conn = get_pg_pool().get().await?;
let organization_id = get_user_organization_id(&user.id).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::organization_id.eq(organization_id))
.filter(dataset_groups::deleted_at.is_null()) .filter(dataset_groups::deleted_at.is_null())
.order_by(dataset_groups::created_at.desc()) .order_by(dataset_groups::created_at.desc())
.load(&mut *conn) .load::<(Uuid, String, i64, bool)>(&mut *conn)
.await?; .await?;
Ok(groups Ok(groups
.into_iter() .into_iter()
.map(|group| DatasetGroupInfo { .map(|(id, name, permission_count, assigned)| DatasetGroupInfo {
id: group.id, id,
name: group.name, name,
organization_id: group.organization_id, permission_count,
created_at: group.created_at, assigned,
updated_at: group.updated_at,
}) })
.collect()) .collect())
} }

View File

@ -1,15 +1,15 @@
use anyhow::Result; use anyhow::Result;
use axum::extract::Path;
use axum::http::StatusCode; use axum::http::StatusCode;
use axum::Extension; use axum::Extension;
use chrono::{DateTime, Utc};
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::lib::get_pg_pool; use crate::database::lib::get_pg_pool;
use crate::database::models::{Dataset, User}; use crate::database::models::User;
use crate::database::schema::datasets; use crate::database::schema::{dataset_permissions, datasets};
use crate::routes::rest::ApiResponse; use crate::routes::rest::ApiResponse;
use crate::utils::user::user_info::get_user_organization_id; 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 struct DatasetInfo {
pub id: Uuid, pub id: Uuid,
pub name: String, pub name: String,
pub organization_id: Uuid, pub assigned: bool,
pub data_source_id: Uuid,
pub enabled: bool,
pub imported: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
} }
pub async fn list_datasets( pub async fn list_datasets(
Extension(user): Extension<User>, Extension(user): Extension<User>,
Path(id): Path<Uuid>,
) -> Result<ApiResponse<Vec<DatasetInfo>>, (StatusCode, &'static str)> { ) -> 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, Ok(datasets) => datasets,
Err(e) => { Err(e) => {
tracing::error!("Error listing datasets: {:?}", e); tracing::error!("Error listing datasets: {:?}", e);
@ -39,28 +35,37 @@ pub async fn list_datasets(
Ok(ApiResponse::JsonData(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 mut conn = get_pg_pool().get().await?;
let organization_id = get_user_organization_id(&user.id).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::organization_id.eq(organization_id))
.filter(datasets::deleted_at.is_null()) .filter(datasets::deleted_at.is_null())
.order_by(datasets::created_at.desc()) .select((
.load(&mut *conn) datasets::id,
.await?; 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 Ok(datasets
.into_iter() .into_iter()
.map(|dataset| DatasetInfo { .map(|(id, name, assigned)| DatasetInfo { id, name, assigned })
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,
})
.collect()) .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")), 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(Value::String(role)) => role,
Some(_) => return Err(anyhow::anyhow!("User role not found")), Some(_) => return Err(anyhow::anyhow!("User role not found")),
None => return Err(anyhow::anyhow!("User role not found")), None => return Err(anyhow::anyhow!("User role not found")),