mirror of https://github.com/buster-so/buster.git
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:
commit
909a7edd95
|
@ -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;
|
|
@ -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);
|
|
@ -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>>,
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")),
|
||||||
|
|
Loading…
Reference in New Issue