diff --git a/api/libs/handlers/src/collections/get_collection_handler.rs b/api/libs/handlers/src/collections/get_collection_handler.rs index 0ccd73e67..def95f001 100644 --- a/api/libs/handlers/src/collections/get_collection_handler.rs +++ b/api/libs/handlers/src/collections/get_collection_handler.rs @@ -80,7 +80,7 @@ pub async fn get_collection_handler( // Check if user has permission to view the collection // Users need at least CanView permission or any higher permission - let has_permission = check_permission_access( + if !check_permission_access( collection_with_permission.permission, &[ AssetPermissionRole::CanView, @@ -90,12 +90,15 @@ pub async fn get_collection_handler( ], collection_with_permission.collection.organization_id, &user.organizations, - ); - - if !has_permission { + ) { return Err(anyhow!("You don't have permission to view this collection")); } + // Extract permission for consistent use in response + // If the asset is public and the user has no direct permission, default to CanView + let permission = collection_with_permission.permission + .unwrap_or(AssetPermissionRole::CanView); + let mut conn = match get_pg_pool().get().await { Ok(conn) => conn, Err(e) => return Err(anyhow!("Failed to get database connection: {}", e)), @@ -232,7 +235,7 @@ pub async fn get_collection_handler( let collection_state = CollectionState { collection: collection_with_permission.collection, assets: Some(formatted_assets), - permission: collection_with_permission.permission.unwrap_or(AssetPermissionRole::Owner), + permission, organization_permissions: false, // TODO: Implement organization permissions individual_permissions, publicly_accessible, diff --git a/api/libs/handlers/src/collections/mod.rs b/api/libs/handlers/src/collections/mod.rs index c9e0cfff9..975ffc94c 100644 --- a/api/libs/handlers/src/collections/mod.rs +++ b/api/libs/handlers/src/collections/mod.rs @@ -11,6 +11,7 @@ pub mod sharing; // Re-export types pub use types::*; +pub use types::GetCollectionRequest; // Re-export handlers pub use add_assets_to_collection_handler::{add_assets_to_collection_handler, AssetToAdd, AddAssetsToCollectionResult}; diff --git a/api/libs/handlers/src/dashboards/get_dashboard_handler.rs b/api/libs/handlers/src/dashboards/get_dashboard_handler.rs index 8de950eb6..d56c9c155 100644 --- a/api/libs/handlers/src/dashboards/get_dashboard_handler.rs +++ b/api/libs/handlers/src/dashboards/get_dashboard_handler.rs @@ -90,7 +90,7 @@ pub async fn get_dashboard_handler( // Check if user has permission to view the dashboard // Users need at least CanView permission or any higher permission - let has_permission = check_permission_access( + if !check_permission_access( dashboard_with_permission.permission, &[ AssetPermissionRole::CanView, @@ -100,12 +100,15 @@ pub async fn get_dashboard_handler( ], dashboard_with_permission.dashboard_file.organization_id, &user.organizations, - ); - - if !has_permission { + ) { return Err(anyhow!("You don't have permission to view this dashboard")); } + // Extract permission for consistent use in response + // If the asset is public and the user has no direct permission, default to CanView + let permission = dashboard_with_permission.permission + .unwrap_or(AssetPermissionRole::CanView); + let mut conn = match get_pg_pool().get().await { Ok(conn) => conn, Err(e) => return Err(anyhow!("Failed to get database connection: {}", e)), @@ -282,14 +285,10 @@ pub async fn get_dashboard_handler( }; Ok(BusterDashboardResponse { - access: dashboard_with_permission - .permission - .unwrap_or(AssetPermissionRole::Owner), + access: permission, metrics, dashboard, - permission: dashboard_with_permission - .permission - .unwrap_or(AssetPermissionRole::Owner), + permission, public_password: None, collections, // Now populated with associated collections // New sharing fields diff --git a/api/libs/handlers/src/metrics/get_metric_handler.rs b/api/libs/handlers/src/metrics/get_metric_handler.rs index 81d9e5b6e..0598a8180 100644 --- a/api/libs/handlers/src/metrics/get_metric_handler.rs +++ b/api/libs/handlers/src/metrics/get_metric_handler.rs @@ -88,36 +88,33 @@ pub async fn get_metric_handler( .await .map_err(|e| anyhow!("Failed to fetch metric file with permissions: {}", e))?; - let metric_file = if let Some(metric_file) = metric_file_with_permission { + let metric_file_with_permission = if let Some(metric_file) = metric_file_with_permission { metric_file } else { return Err(anyhow!("Metric file not found")); }; - println!("metric_file: {:?}", metric_file.permission); - - // 2. Check if user has at least FullAccess permission + // 2. Check if user has at least CanView permission if !check_permission_access( - metric_file.permission, + metric_file_with_permission.permission, &[ AssetPermissionRole::FullAccess, AssetPermissionRole::Owner, AssetPermissionRole::CanEdit, AssetPermissionRole::CanView, ], - metric_file.metric_file.organization_id, + metric_file_with_permission.metric_file.organization_id, &user.organizations, ) { return Err(anyhow!("You don't have permission to view this metric")); } - let permission = if let Some(permission) = metric_file.permission { - permission - } else { - return Err(anyhow!("You don't have permission to view this metric")); - }; + // 3. Extract permission for consistent use in response + // If the asset is public and the user has no direct permission, default to CanView + let permission = metric_file_with_permission.permission + .unwrap_or(AssetPermissionRole::CanView); - let metric_file = metric_file.metric_file; + let metric_file = metric_file_with_permission.metric_file; // Map evaluation score to High/Moderate/Low let evaluation_score = metric_file.evaluation_score.map(|score| { diff --git a/api/libs/handlers/tests/collections/mod.rs b/api/libs/handlers/tests/collections/mod.rs new file mode 100644 index 000000000..d00354deb --- /dev/null +++ b/api/libs/handlers/tests/collections/mod.rs @@ -0,0 +1,2 @@ +// Test modules +pub mod permission_field_test; \ No newline at end of file diff --git a/api/libs/handlers/tests/collections/permission_field_test.rs b/api/libs/handlers/tests/collections/permission_field_test.rs new file mode 100644 index 000000000..9bec5fa5c --- /dev/null +++ b/api/libs/handlers/tests/collections/permission_field_test.rs @@ -0,0 +1,227 @@ +use anyhow::Result; +use database::enums::{AssetPermissionRole, AssetType, IdentityType}; +use database::models::{AssetPermission, Collection}; +use database::pool::get_pg_pool; +use database::schema::{asset_permissions, collections}; +use database::models::UserToOrganization; +use diesel::prelude::*; +use diesel_async::RunQueryDsl; +use handlers::collections::{get_collection_handler, GetCollectionRequest}; +use uuid::Uuid; + +/// Helper function to create a test collection +async fn create_test_collection( + organization_id: Uuid, + user_id: Uuid, + name: &str, +) -> Result { + let mut conn = get_pg_pool().get().await?; + let collection_id = Uuid::new_v4(); + + let collection = Collection { + id: collection_id, + name: name.to_string(), + description: Some(format!("Test collection description for {}", name)), + created_by: user_id, + updated_by: user_id, + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + deleted_at: None, + organization_id, + }; + + diesel::insert_into(collections::table) + .values(&collection) + .execute(&mut conn) + .await?; + + Ok(collection) +} + +/// Helper function to add permission for a collection +async fn add_permission( + asset_id: Uuid, + user_id: Uuid, + role: AssetPermissionRole, + created_by: Uuid, +) -> Result<()> { + let mut conn = get_pg_pool().get().await?; + + let permission = AssetPermission { + identity_id: user_id, + identity_type: IdentityType::User, + asset_id, + asset_type: AssetType::Collection, + role, + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + deleted_at: None, + created_by, + updated_by: created_by, + }; + + diesel::insert_into(asset_permissions::table) + .values(&permission) + .execute(&mut conn) + .await?; + + Ok(()) +} + +/// Test to ensure permission field in collection response matches the permission used for access control +#[tokio::test] +async fn test_collection_permission_field_consistency() -> Result<()> { + // Create user and organization for testing + let user_id = Uuid::new_v4(); + let org_id = Uuid::new_v4(); + + // Create test collection + let collection = create_test_collection( + org_id, + user_id, + "Test Permission Collection" + ).await?; + + // Add permission for asset + add_permission( + collection.id, + user_id, + AssetPermissionRole::Owner, + user_id + ).await?; + + // Create middleware user + let middleware_user = middleware::AuthenticatedUser { + id: user_id, + email: "test@example.com".to_string(), + name: Some("Test User".to_string()), + config: serde_json::json!({}), + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + attributes: serde_json::json!({}), + avatar_url: None, + organizations: vec![ + middleware::OrganizationMembership { + id: org_id, + role: database::enums::UserOrganizationRole::WorkspaceAdmin, + }, + ], + teams: vec![], + }; + + // Create request data + let request = GetCollectionRequest { + id: collection.id, + }; + + // Get collection with the user who has owner permission + let collection_response = get_collection_handler(&middleware_user, request).await?; + + // Check if permission field matches what we set + assert_eq!(collection_response.permission, AssetPermissionRole::Owner); + + Ok(()) +} + +/// Test to ensure access is denied for users without permissions +#[tokio::test] +async fn test_collection_permission_denied() -> Result<()> { + // Create user and organization for testing + let owner_id = Uuid::new_v4(); + let org_id = Uuid::new_v4(); + + // Create a private test collection + let collection = create_test_collection( + org_id, + owner_id, + "Private Collection" + ).await?; + + // Create another user in a different organization + let other_user_id = Uuid::new_v4(); + let other_org_id = Uuid::new_v4(); + + // Create middleware user for other user + let other_middleware_user = middleware::AuthenticatedUser { + id: other_user_id, + email: "other@example.com".to_string(), + name: Some("Other User".to_string()), + config: serde_json::json!({}), + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + attributes: serde_json::json!({}), + avatar_url: None, + organizations: vec![ + middleware::OrganizationMembership { + id: other_org_id, + role: database::enums::UserOrganizationRole::Viewer, + }, + ], + teams: vec![], + }; + + // Create request data + let request = GetCollectionRequest { + id: collection.id, + }; + + // Try to get collection with a user who has no permissions + let result = get_collection_handler(&other_middleware_user, request).await; + + // Should be denied access + assert!(result.is_err()); + let error = result.unwrap_err(); + assert!(error.to_string().contains("You don't have permission")); + + Ok(()) +} + +/// Test inherited permissions through organization role +#[tokio::test] +async fn test_collection_org_admin_permission() -> Result<()> { + // Create user and organization for testing + let user_id = Uuid::new_v4(); + let org_id = Uuid::new_v4(); + + // Create test collection (without direct permission) + let collection = create_test_collection( + org_id, + user_id, + "Admin Collection" + ).await?; + + // Create middleware user with admin role + let middleware_user = middleware::AuthenticatedUser { + id: user_id, + email: "admin@example.com".to_string(), + name: Some("Admin User".to_string()), + config: serde_json::json!({}), + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + attributes: serde_json::json!({}), + avatar_url: None, + organizations: vec![ + middleware::OrganizationMembership { + id: org_id, + role: database::enums::UserOrganizationRole::WorkspaceAdmin, + }, + ], + teams: vec![], + }; + + // Create request data + let request = GetCollectionRequest { + id: collection.id, + }; + + // Get collection with the admin user + let result = get_collection_handler(&middleware_user, request).await; + + // Access should be successful and admin should have Owner permission + assert!(result.is_ok(), "Admin should have access: {:?}", result.err()); + if let Ok(response) = result { + assert_eq!(response.permission, AssetPermissionRole::Owner); + } + + Ok(()) +} \ No newline at end of file diff --git a/api/libs/handlers/tests/dashboards/mod.rs b/api/libs/handlers/tests/dashboards/mod.rs index 1d0a498f9..a91d61dd0 100644 --- a/api/libs/handlers/tests/dashboards/mod.rs +++ b/api/libs/handlers/tests/dashboards/mod.rs @@ -1 +1,2 @@ -mod list_sharing_test; \ No newline at end of file +mod list_sharing_test; +mod permission_field_test; \ No newline at end of file diff --git a/api/libs/handlers/tests/dashboards/permission_field_test.rs b/api/libs/handlers/tests/dashboards/permission_field_test.rs new file mode 100644 index 000000000..9fa9db7b5 --- /dev/null +++ b/api/libs/handlers/tests/dashboards/permission_field_test.rs @@ -0,0 +1,265 @@ +use anyhow::Result; +use database::enums::{AssetPermissionRole, AssetType, IdentityType}; +use database::models::{AssetPermission, DashboardFile}; +use database::pool::get_pg_pool; +use database::schema::{asset_permissions, dashboard_files}; +use database::models::UserToOrganization; +use database::types::{DashboardYml, VersionHistory}; +use diesel::prelude::*; +use diesel_async::RunQueryDsl; +use handlers::dashboards::get_dashboard_handler; +use std::collections::HashMap; +use uuid::Uuid; + +/// Helper function to create a test dashboard file +async fn create_test_dashboard( + organization_id: Uuid, + user_id: Uuid, + name: &str, +) -> Result { + let mut conn = get_pg_pool().get().await?; + let dashboard_id = Uuid::new_v4(); + + // Create a simple dashboard content + let content = DashboardYml { + name: name.to_string(), + description: Some(format!("Test dashboard description for {}", name)), + rows: Vec::new(), + }; + + let dashboard_file = DashboardFile { + id: dashboard_id, + name: name.to_string(), + file_name: format!("{}.yml", name.to_lowercase().replace(" ", "_")), + content, + filter: None, + organization_id, + created_by: user_id, + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + deleted_at: None, + publicly_accessible: false, + publicly_enabled_by: None, + public_expiry_date: None, + version_history: VersionHistory(HashMap::new()), + public_password: None, + }; + + diesel::insert_into(dashboard_files::table) + .values(&dashboard_file) + .execute(&mut conn) + .await?; + + Ok(dashboard_file) +} + +/// Helper function to add permission for a dashboard +async fn add_permission( + asset_id: Uuid, + user_id: Uuid, + role: AssetPermissionRole, + created_by: Uuid, +) -> Result<()> { + let mut conn = get_pg_pool().get().await?; + + let permission = AssetPermission { + identity_id: user_id, + identity_type: IdentityType::User, + asset_id, + asset_type: AssetType::DashboardFile, + role, + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + deleted_at: None, + created_by, + updated_by: created_by, + }; + + diesel::insert_into(asset_permissions::table) + .values(&permission) + .execute(&mut conn) + .await?; + + Ok(()) +} + +/// Test to ensure permission fields in dashboard response match the permission used for access control +#[tokio::test] +async fn test_dashboard_permission_field_consistency() -> Result<()> { + // Create user and organization for testing + let user_id = Uuid::new_v4(); + let org_id = Uuid::new_v4(); + + // Create test dashboard + let dashboard = create_test_dashboard( + org_id, + user_id, + "Test Permission Dashboard" + ).await?; + + // Add permission for asset + add_permission( + dashboard.id, + user_id, + AssetPermissionRole::Owner, + user_id + ).await?; + + // Create middleware user + let middleware_user = middleware::AuthenticatedUser { + id: user_id, + email: "test@example.com".to_string(), + name: Some("Test User".to_string()), + config: serde_json::json!({}), + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + attributes: serde_json::json!({}), + avatar_url: None, + organizations: vec![ + middleware::OrganizationMembership { + id: org_id, + role: database::enums::UserOrganizationRole::WorkspaceAdmin, + }, + ], + teams: vec![], + }; + + // Get dashboard with the user who has owner permission + let dashboard_response = get_dashboard_handler(&dashboard.id, &middleware_user, None).await?; + + // Check if permission fields are consistent + assert_eq!(dashboard_response.permission, AssetPermissionRole::Owner); + assert_eq!(dashboard_response.access, AssetPermissionRole::Owner); + + Ok(()) +} + +/// Test to ensure public dashboards grant CanView permission to users without direct permissions +#[tokio::test] +async fn test_public_dashboard_permission_field() -> Result<()> { + // Create user and organization for testing + let owner_id = Uuid::new_v4(); + let org_id = Uuid::new_v4(); + + // Create test dashboard + let dashboard = create_test_dashboard( + org_id, + owner_id, + "Public Dashboard" + ).await?; + + // Make dashboard public + let mut conn = get_pg_pool().get().await?; + diesel::update(dashboard_files::table) + .filter(dashboard_files::id.eq(dashboard.id)) + .set(( + dashboard_files::publicly_accessible.eq(true), + dashboard_files::publicly_enabled_by.eq(Some(owner_id)), + dashboard_files::public_expiry_date.eq(Some(chrono::Utc::now() + chrono::Duration::days(7))), + )) + .execute(&mut conn) + .await?; + + // Create another user + let other_user_id = Uuid::new_v4(); + + // Add user to organization with viewer role + let user_org = UserToOrganization { + user_id: other_user_id, + organization_id: org_id, + role: database::enums::UserOrganizationRole::Viewer, + sharing_setting: database::enums::SharingSetting::None, + edit_sql: true, + upload_csv: true, + export_assets: true, + email_slack_enabled: true, + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + deleted_at: None, + created_by: owner_id, + updated_by: owner_id, + deleted_by: None, + status: database::enums::UserOrganizationStatus::Active, + }; + + diesel::insert_into(database::schema::users_to_organizations::table) + .values(&user_org) + .execute(&mut conn) + .await?; + + // Create middleware user for other user + let other_middleware_user = middleware::AuthenticatedUser { + id: other_user_id, + email: "other@example.com".to_string(), + name: Some("Other User".to_string()), + config: serde_json::json!({}), + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + attributes: serde_json::json!({}), + avatar_url: None, + organizations: vec![ + middleware::OrganizationMembership { + id: org_id, + role: database::enums::UserOrganizationRole::Viewer, + }, + ], + teams: vec![], + }; + + // Get dashboard with the other user who doesn't have direct permission + let dashboard_response = get_dashboard_handler(&dashboard.id, &other_middleware_user, None).await?; + + // Public assets should have CanView permission by default + assert_eq!(dashboard_response.permission, AssetPermissionRole::CanView); + assert_eq!(dashboard_response.access, AssetPermissionRole::CanView); + + Ok(()) +} + +/// Test to ensure access is denied for users without permissions and non-public dashboards +#[tokio::test] +async fn test_dashboard_permission_denied() -> Result<()> { + // Create user and organization for testing + let owner_id = Uuid::new_v4(); + let org_id = Uuid::new_v4(); + + // Create a private test dashboard + let dashboard = create_test_dashboard( + org_id, + owner_id, + "Private Dashboard" + ).await?; + + // Create another user in a different organization + let other_user_id = Uuid::new_v4(); + let other_org_id = Uuid::new_v4(); + + // Create middleware user for other user + let other_middleware_user = middleware::AuthenticatedUser { + id: other_user_id, + email: "other@example.com".to_string(), + name: Some("Other User".to_string()), + config: serde_json::json!({}), + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + attributes: serde_json::json!({}), + avatar_url: None, + organizations: vec![ + middleware::OrganizationMembership { + id: other_org_id, + role: database::enums::UserOrganizationRole::Viewer, + }, + ], + teams: vec![], + }; + + // Try to get dashboard with a user who has no permissions + let result = get_dashboard_handler(&dashboard.id, &other_middleware_user, None).await; + + // Should be denied access + assert!(result.is_err()); + let error = result.unwrap_err(); + assert!(error.to_string().contains("You don't have permission")); + + Ok(()) +} \ No newline at end of file diff --git a/api/libs/handlers/tests/metrics/mod.rs b/api/libs/handlers/tests/metrics/mod.rs new file mode 100644 index 000000000..d00354deb --- /dev/null +++ b/api/libs/handlers/tests/metrics/mod.rs @@ -0,0 +1,2 @@ +// Test modules +pub mod permission_field_test; \ No newline at end of file diff --git a/api/libs/handlers/tests/metrics/permission_field_test.rs b/api/libs/handlers/tests/metrics/permission_field_test.rs new file mode 100644 index 000000000..8f20a14ce --- /dev/null +++ b/api/libs/handlers/tests/metrics/permission_field_test.rs @@ -0,0 +1,298 @@ +use anyhow::Result; +use database::enums::{AssetPermissionRole, AssetType, IdentityType}; +use database::models::{AssetPermission, MetricFile}; +use database::pool::get_pg_pool; +use database::schema::{asset_permissions, metric_files}; +use database::models::UserToOrganization; +use database::types::metric_yml::{BarAndLineAxis, BarLineChartConfig, BaseChartConfig}; +use database::types::{ChartConfig, MetricYml, VersionHistory}; +use diesel::prelude::*; +use diesel_async::RunQueryDsl; +use handlers::metrics::get_metric_handler; +use std::collections::HashMap; +use uuid::Uuid; + +/// Helper function to create a test metric file +async fn create_test_metric( + organization_id: Uuid, + user_id: Uuid, + name: &str, +) -> Result { + let mut conn = get_pg_pool().get().await?; + let metric_id = Uuid::new_v4(); + + // Create a simple metric content + let content = MetricYml { + name: name.to_string(), + description: Some(format!("Test metric description for {}", name)), + sql: "SELECT * FROM test".to_string(), + time_frame: "last 30 days".to_string(), + chart_config: ChartConfig::Bar(BarLineChartConfig { + base: BaseChartConfig { + column_label_formats: indexmap::IndexMap::new(), + column_settings: None, + colors: None, + show_legend: None, + grid_lines: None, + show_legend_headline: None, + goal_lines: None, + trendlines: None, + disable_tooltip: None, + y_axis_config: None, + x_axis_config: None, + category_axis_style_config: None, + y2_axis_config: None, + }, + bar_and_line_axis: BarAndLineAxis { + x: vec!["x".to_string()], + y: vec!["y".to_string()], + category: None, + tooltip: None, + }, + bar_layout: None, + bar_sort_by: None, + bar_group_type: None, + bar_show_total_at_top: None, + line_group_type: None, + }), + dataset_ids: Vec::new(), + }; + + let metric_file = MetricFile { + id: metric_id, + name: name.to_string(), + file_name: format!("{}.yml", name.to_lowercase().replace(" ", "_")), + content, + verification: database::enums::Verification::Verified, + evaluation_obj: None, + evaluation_summary: None, + evaluation_score: None, + organization_id, + created_by: user_id, + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + deleted_at: None, + publicly_accessible: false, + publicly_enabled_by: None, + public_expiry_date: None, + version_history: VersionHistory(HashMap::new()), + data_metadata: None, + public_password: None, + }; + + diesel::insert_into(metric_files::table) + .values(&metric_file) + .execute(&mut conn) + .await?; + + Ok(metric_file) +} + +/// Helper function to add permission for a metric +async fn add_permission( + asset_id: Uuid, + user_id: Uuid, + role: AssetPermissionRole, + created_by: Uuid, +) -> Result<()> { + let mut conn = get_pg_pool().get().await?; + + let permission = AssetPermission { + identity_id: user_id, + identity_type: IdentityType::User, + asset_id, + asset_type: AssetType::MetricFile, + role, + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + deleted_at: None, + created_by, + updated_by: created_by, + }; + + diesel::insert_into(asset_permissions::table) + .values(&permission) + .execute(&mut conn) + .await?; + + Ok(()) +} + +/// Test to ensure permission field in the response matches the permission used for access control +#[tokio::test] +async fn test_metric_permission_field_consistency() -> Result<()> { + // Create user and organization for testing + let user_id = Uuid::new_v4(); + let org_id = Uuid::new_v4(); + + // Create test metric + let metric = create_test_metric( + org_id, + user_id, + "Test Permission Metric" + ).await?; + + // Add permission for asset + add_permission( + metric.id, + user_id, + AssetPermissionRole::Owner, + user_id + ).await?; + + // Create middleware user + let middleware_user = middleware::AuthenticatedUser { + id: user_id, + email: "test@example.com".to_string(), + name: Some("Test User".to_string()), + config: serde_json::json!({}), + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + attributes: serde_json::json!({}), + avatar_url: None, + organizations: vec![ + middleware::OrganizationMembership { + id: org_id, + role: database::enums::UserOrganizationRole::WorkspaceAdmin, + }, + ], + teams: vec![], + }; + + // Get metric with the user who has owner permission + let metric_response = get_metric_handler(&metric.id, &middleware_user, None).await?; + + // Check if permission field matches what we set + assert_eq!(metric_response.permission, AssetPermissionRole::Owner); + + Ok(()) +} + +/// Test to ensure public metrics grant CanView permission to users without direct permissions +#[tokio::test] +async fn test_public_metric_permission_field() -> Result<()> { + // Create user and organization for testing + let owner_id = Uuid::new_v4(); + let org_id = Uuid::new_v4(); + + // Create test metric + let metric = create_test_metric( + org_id, + owner_id, + "Public Metric" + ).await?; + + // Make metric public + let mut conn = get_pg_pool().get().await?; + diesel::update(metric_files::table) + .filter(metric_files::id.eq(metric.id)) + .set(( + metric_files::publicly_accessible.eq(true), + metric_files::publicly_enabled_by.eq(Some(owner_id)), + metric_files::public_expiry_date.eq(Some(chrono::Utc::now() + chrono::Duration::days(7))), + )) + .execute(&mut conn) + .await?; + + // Create another user + let other_user_id = Uuid::new_v4(); + + // Add user to organization with viewer role + let user_org = UserToOrganization { + user_id: other_user_id, + organization_id: org_id, + role: database::enums::UserOrganizationRole::Viewer, + sharing_setting: database::enums::SharingSetting::None, + edit_sql: true, + upload_csv: true, + export_assets: true, + email_slack_enabled: true, + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + deleted_at: None, + created_by: owner_id, + updated_by: owner_id, + deleted_by: None, + status: database::enums::UserOrganizationStatus::Active, + }; + + diesel::insert_into(database::schema::users_to_organizations::table) + .values(&user_org) + .execute(&mut conn) + .await?; + + // Create middleware user for other user + let other_middleware_user = middleware::AuthenticatedUser { + id: other_user_id, + email: "other@example.com".to_string(), + name: Some("Other User".to_string()), + config: serde_json::json!({}), + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + attributes: serde_json::json!({}), + avatar_url: None, + organizations: vec![ + middleware::OrganizationMembership { + id: org_id, + role: database::enums::UserOrganizationRole::Viewer, + }, + ], + teams: vec![], + }; + + // Get metric with the other user who doesn't have direct permission + let metric_response = get_metric_handler(&metric.id, &other_middleware_user, None).await?; + + // Public assets should have CanView permission by default + assert_eq!(metric_response.permission, AssetPermissionRole::CanView); + + Ok(()) +} + +/// Test to ensure access is denied for users without permissions and non-public metrics +#[tokio::test] +async fn test_metric_permission_denied() -> Result<()> { + // Create user and organization for testing + let owner_id = Uuid::new_v4(); + let org_id = Uuid::new_v4(); + + // Create a private test metric + let metric = create_test_metric( + org_id, + owner_id, + "Private Metric" + ).await?; + + // Create another user in a different organization + let other_user_id = Uuid::new_v4(); + let other_org_id = Uuid::new_v4(); + + // Create middleware user for other user + let other_middleware_user = middleware::AuthenticatedUser { + id: other_user_id, + email: "other@example.com".to_string(), + name: Some("Other User".to_string()), + config: serde_json::json!({}), + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + attributes: serde_json::json!({}), + avatar_url: None, + organizations: vec![ + middleware::OrganizationMembership { + id: other_org_id, + role: database::enums::UserOrganizationRole::Viewer, + }, + ], + teams: vec![], + }; + + // Try to get metric with a user who has no permissions + let result = get_metric_handler(&metric.id, &other_middleware_user, None).await; + + // Should be denied access + assert!(result.is_err()); + let error = result.unwrap_err(); + assert!(error.to_string().contains("You don't have permission")); + + Ok(()) +} \ No newline at end of file diff --git a/api/libs/handlers/tests/mod.rs b/api/libs/handlers/tests/mod.rs index ff209a4a1..b4b9a8b39 100644 --- a/api/libs/handlers/tests/mod.rs +++ b/api/libs/handlers/tests/mod.rs @@ -30,3 +30,5 @@ fn init_test_env() { // Test modules pub mod dashboards; +pub mod metrics; +pub mod collections;