get max permissions between direct and workspace

This commit is contained in:
dal 2025-07-17 13:14:57 -06:00
parent a0a1e11493
commit 955aab3232
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
7 changed files with 135 additions and 69 deletions

View File

@ -92,6 +92,8 @@ pub enum TeamToUserRole {
Hash, Hash,
diesel::AsExpression, diesel::AsExpression,
diesel::FromSqlRow, diesel::FromSqlRow,
Ord,
PartialOrd,
)] )]
#[diesel(sql_type = sql_types::AssetPermissionRoleEnum)] #[diesel(sql_type = sql_types::AssetPermissionRoleEnum)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]

View File

@ -12,7 +12,7 @@ use database::{
use diesel::{ExpressionMethods, JoinOnDsl, NullableExpressionMethods, QueryDsl, Queryable}; use diesel::{ExpressionMethods, JoinOnDsl, NullableExpressionMethods, QueryDsl, Queryable};
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use middleware::AuthenticatedUser; use middleware::AuthenticatedUser;
use sharing::check_permission_access; use sharing::{check_permission_access, compute_effective_permission};
use tracing; use tracing;
use uuid::Uuid; use uuid::Uuid;
@ -96,22 +96,22 @@ pub async fn get_collection_handler(
return Err(anyhow!("You don't have permission to view this collection")); return Err(anyhow!("You don't have permission to view this collection"));
} }
// Extract permission for consistent use in response // Compute the effective permission (highest of direct and workspace sharing)
// If the asset is public and the user has no direct permission, default to CanView let permission = compute_effective_permission(
let mut permission = collection_with_permission.permission collection_with_permission.permission,
.unwrap_or(AssetPermissionRole::CanView); collection_with_permission.collection.workspace_sharing,
collection_with_permission.collection.organization_id,
// Check if user is WorkspaceAdmin or DataAdmin for this organization &user.organizations,
let is_admin = user.organizations.iter().any(|org| { ).unwrap_or(AssetPermissionRole::CanView);
org.id == collection_with_permission.collection.organization_id
&& (org.role == database::enums::UserOrganizationRole::WorkspaceAdmin tracing::debug!(
|| org.role == database::enums::UserOrganizationRole::DataAdmin) collection_id = %req.id,
}); user_id = %user.id,
direct_permission = ?collection_with_permission.permission,
if is_admin { workspace_sharing = ?collection_with_permission.collection.workspace_sharing,
// Admin users get Owner permissions effective_permission = ?permission,
permission = AssetPermissionRole::Owner; "Computed effective permission for collection"
} );
let mut conn = match get_pg_pool().get().await { let mut conn = match get_pg_pool().get().await {
Ok(conn) => conn, Ok(conn) => conn,

View File

@ -21,7 +21,7 @@ use database::schema::{
asset_permissions, collections, collections_to_assets, dashboard_files, metric_files, users, asset_permissions, collections, collections_to_assets, dashboard_files, metric_files, users,
}; };
use database::types::{MetricYml, VersionHistory}; use database::types::{MetricYml, VersionHistory};
use sharing::check_permission_access; use sharing::{check_permission_access, compute_effective_permission};
use super::{ use super::{
BusterDashboard, BusterDashboardResponse, DashboardConfig, DashboardRow, DashboardRowItem, BusterDashboard, BusterDashboardResponse, DashboardConfig, DashboardRow, DashboardRowItem,
@ -131,22 +131,23 @@ pub async fn get_dashboard_handler(
tracing::debug!(dashboard_id = %dashboard_id, ?direct_permission_level, has_sufficient_direct_permission, "Direct permission check result"); tracing::debug!(dashboard_id = %dashboard_id, ?direct_permission_level, has_sufficient_direct_permission, "Direct permission check result");
if has_sufficient_direct_permission { if has_sufficient_direct_permission {
// Check if user is WorkspaceAdmin or DataAdmin for this organization // Compute the effective permission (highest of direct and workspace sharing)
let is_admin = user.organizations.iter().any(|org| { let effective_permission = compute_effective_permission(
org.id == dashboard_file.organization_id direct_permission_level,
&& (org.role == database::enums::UserOrganizationRole::WorkspaceAdmin dashboard_file.workspace_sharing,
|| org.role == database::enums::UserOrganizationRole::DataAdmin) dashboard_file.organization_id,
}); &user.organizations,
);
if is_admin {
// Admin users get Owner permissions permission = effective_permission.unwrap_or(AssetPermissionRole::CanView);
permission = AssetPermissionRole::Owner; tracing::debug!(
tracing::debug!(dashboard_id = %dashboard_id, user_id = %user.id, ?permission, "Granting Owner access to admin user."); dashboard_id = %dashboard_id,
} else { user_id = %user.id,
// User has direct permission, use that role ?direct_permission_level,
permission = direct_permission_level.unwrap_or(AssetPermissionRole::CanView); // Default just in case workspace_sharing = ?dashboard_file.workspace_sharing,
tracing::debug!(dashboard_id = %dashboard_id, user_id = %user.id, ?permission, "Granting access via direct permission."); ?permission,
} "Granting access with effective permission (max of direct and workspace sharing)."
);
} else { } else {
// No sufficient direct/admin permission, check if user has access via a chat // No sufficient direct/admin permission, check if user has access via a chat
tracing::debug!(dashboard_id = %dashboard_id, "Insufficient direct/admin permission. Checking chat access."); tracing::debug!(dashboard_id = %dashboard_id, "Insufficient direct/admin permission. Checking chat access.");

View File

@ -17,7 +17,7 @@ use database::schema::{
asset_permissions, collections, collections_to_assets, dashboard_files, datasets, metric_files, asset_permissions, collections, collections_to_assets, dashboard_files, datasets, metric_files,
metric_files_to_dashboard_files, metric_files_to_datasets, users, metric_files_to_dashboard_files, metric_files_to_datasets, users,
}; };
use sharing::check_permission_access; use sharing::{check_permission_access, compute_effective_permission};
use super::Version; use super::Version;
@ -142,22 +142,23 @@ pub async fn get_metric_for_dashboard_handler(
tracing::debug!(metric_id = %metric_id, ?direct_permission_level, has_sufficient_direct_permission, "Direct permission check result"); tracing::debug!(metric_id = %metric_id, ?direct_permission_level, has_sufficient_direct_permission, "Direct permission check result");
if has_sufficient_direct_permission { if has_sufficient_direct_permission {
// Check if user is WorkspaceAdmin or DataAdmin for this organization // Compute the effective permission (highest of direct and workspace sharing)
let is_admin = user.organizations.iter().any(|org| { let effective_permission = compute_effective_permission(
org.id == metric_file.organization_id direct_permission_level,
&& (org.role == database::enums::UserOrganizationRole::WorkspaceAdmin metric_file.workspace_sharing,
|| org.role == database::enums::UserOrganizationRole::DataAdmin) metric_file.organization_id,
}); &user.organizations,
);
if is_admin {
// Admin users get Owner permissions permission = effective_permission.unwrap_or(AssetPermissionRole::CanView);
permission = AssetPermissionRole::Owner; tracing::debug!(
tracing::debug!(metric_id = %metric_id, user_id = %user.id, ?permission, "Granting Owner access to admin user."); metric_id = %metric_id,
} else { user_id = %user.id,
// User has direct permission, use that role ?direct_permission_level,
permission = direct_permission_level.unwrap_or(AssetPermissionRole::CanView); // Default just in case workspace_sharing = ?metric_file.workspace_sharing,
tracing::debug!(metric_id = %metric_id, user_id = %user.id, ?permission, "Granting access via direct permission."); ?permission,
} "Granting access with effective permission (max of direct and workspace sharing)."
);
} else { } else {
// No sufficient direct/admin permission, check if user has access via a dashboard // No sufficient direct/admin permission, check if user has access via a dashboard
tracing::debug!(metric_id = %metric_id, "Insufficient direct/admin permission. Checking dashboard access."); tracing::debug!(metric_id = %metric_id, "Insufficient direct/admin permission. Checking dashboard access.");

View File

@ -15,7 +15,7 @@ use database::schema::{
asset_permissions, collections, collections_to_assets, dashboard_files, datasets, asset_permissions, collections, collections_to_assets, dashboard_files, datasets,
metric_files_to_dashboard_files, users, metric_files_to_datasets, metric_files_to_dashboard_files, users, metric_files_to_datasets,
}; };
use sharing::check_permission_access; use sharing::{check_permission_access, compute_effective_permission};
use super::Version; use super::Version;
@ -140,22 +140,23 @@ pub async fn get_metric_handler(
tracing::debug!(metric_id = %metric_id, ?direct_permission_level, has_sufficient_direct_permission, "Direct permission check result"); tracing::debug!(metric_id = %metric_id, ?direct_permission_level, has_sufficient_direct_permission, "Direct permission check result");
if has_sufficient_direct_permission { if has_sufficient_direct_permission {
// Check if user is WorkspaceAdmin or DataAdmin for this organization // Compute the effective permission (highest of direct and workspace sharing)
let is_admin = user.organizations.iter().any(|org| { let effective_permission = compute_effective_permission(
org.id == metric_file.organization_id direct_permission_level,
&& (org.role == database::enums::UserOrganizationRole::WorkspaceAdmin metric_file.workspace_sharing,
|| org.role == database::enums::UserOrganizationRole::DataAdmin) metric_file.organization_id,
}); &user.organizations,
);
if is_admin {
// Admin users get Owner permissions permission = effective_permission.unwrap_or(AssetPermissionRole::CanView);
permission = AssetPermissionRole::Owner; tracing::debug!(
tracing::debug!(metric_id = %metric_id, user_id = %user.id, ?permission, "Granting Owner access to admin user."); metric_id = %metric_id,
} else { user_id = %user.id,
// User has direct permission, use that role ?direct_permission_level,
permission = direct_permission_level.unwrap_or(AssetPermissionRole::CanView); // Default just in case workspace_sharing = ?metric_file.workspace_sharing,
tracing::debug!(metric_id = %metric_id, user_id = %user.id, ?permission, "Granting access via direct permission."); ?permission,
} "Granting access with effective permission (max of direct and workspace sharing)."
);
} else { } else {
// No sufficient direct/admin permission, check if user has access via a dashboard // No sufficient direct/admin permission, check if user has access via a dashboard
tracing::debug!(metric_id = %metric_id, "Insufficient direct/admin permission. Checking dashboard access."); tracing::debug!(metric_id = %metric_id, "Insufficient direct/admin permission. Checking dashboard access.");

View File

@ -5,6 +5,63 @@ use diesel::{BoolExpressionMethods, ExpressionMethods, JoinOnDsl, QueryDsl, Opti
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use middleware::OrganizationMembership; use middleware::OrganizationMembership;
use uuid::Uuid; use uuid::Uuid;
use std::cmp::Ordering;
/// Computes the effective permission level for a user on an asset by taking the maximum
/// of their direct permission and workspace sharing permission.
///
/// # Arguments
/// * `direct_permission` - The user's direct permission on the asset (if any)
/// * `workspace_sharing` - The workspace sharing level for the asset
/// * `organization_id` - UUID of the organization
/// * `organization_role_grants` - User's organization memberships
///
/// # Returns
/// * `Option<AssetPermissionRole>` - The highest permission level available to the user
pub fn compute_effective_permission(
direct_permission: Option<AssetPermissionRole>,
workspace_sharing: WorkspaceSharing,
organization_id: Uuid,
organization_role_grants: &[OrganizationMembership],
) -> Option<AssetPermissionRole> {
// First check if the user has WorkspaceAdmin or DataAdmin role for the organization
for org in organization_role_grants {
if org.id == organization_id
&& (org.role == UserOrganizationRole::WorkspaceAdmin
|| org.role == UserOrganizationRole::DataAdmin)
{
return Some(AssetPermissionRole::Owner);
}
}
// Compute workspace-granted permission
let workspace_permission = if workspace_sharing != WorkspaceSharing::None {
// Check if user is member of the organization
if organization_role_grants.iter().any(|org| org.id == organization_id) {
match workspace_sharing {
WorkspaceSharing::CanView => Some(AssetPermissionRole::CanView),
WorkspaceSharing::CanEdit => Some(AssetPermissionRole::CanEdit),
WorkspaceSharing::FullAccess => Some(AssetPermissionRole::FullAccess),
WorkspaceSharing::None => None,
}
} else {
None
}
} else {
None
};
// Return the highest permission level
match (direct_permission, workspace_permission) {
(Some(direct), Some(workspace)) => {
// Use the max method to get the higher permission
Some(direct.max(workspace))
}
(Some(direct), None) => Some(direct),
(None, Some(workspace)) => Some(workspace),
(None, None) => None,
}
}
/// Checks if a user has sufficient permissions based on organization roles and asset permissions. /// Checks if a user has sufficient permissions based on organization roles and asset permissions.
/// ///

View File

@ -19,4 +19,8 @@ pub use types::{
SerializableAssetPermission, UserInfo, SerializableAssetPermission, UserInfo,
}; };
pub use user_lookup::find_user_by_email; pub use user_lookup::find_user_by_email;
pub use asset_access_checks::{check_permission_access, check_metric_dashboard_access, check_metric_chat_access, check_dashboard_chat_access}; pub use asset_access_checks::{
check_permission_access, check_metric_dashboard_access, check_metric_chat_access,
check_dashboard_chat_access, check_metric_collection_access, check_dashboard_collection_access,
compute_effective_permission
};