ok password finally working

This commit is contained in:
dal 2025-04-07 21:18:20 -06:00
parent d346bca850
commit f5678c4a4d
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
12 changed files with 660 additions and 91 deletions

View File

@ -163,26 +163,28 @@ pub async fn fetch_dashboard_file_with_permission(
None => return Ok(None),
};
// Check if the file is publicly accessible
let is_public = is_publicly_accessible(&dashboard_file).await;
// Check if the file is publicly accessible (we don't grant permission here anymore)
// let is_public = is_publicly_accessible(&dashboard_file).await;
// If collection permission exists, use it; otherwise use direct permission
let mut effective_permission = match collection_permission {
let effective_permission = match collection_permission {
Some(collection) => Some(collection),
None => direct_permission,
};
// If the file is publicly accessible and either no permission exists or it's lower than CanView,
// grant CanView permission
// REMOVED: Logic that automatically granted CanView for public access.
// The handler is now responsible for checking public access rules.
/*
if is_public {
if effective_permission.is_none() {
effective_permission = Some(AssetPermissionRole::CanView);
}
}
*/
Ok(Some(DashboardFileWithPermission {
dashboard_file,
permission: effective_permission,
permission: effective_permission, // Now only reflects direct or collection permission
}))
}
@ -273,27 +275,29 @@ pub async fn fetch_dashboard_files_with_permissions(
let direct_permission = direct_permission_map.get(&dashboard_file.id).cloned();
let collection_permission = collection_permission_map.get(&dashboard_file.id).cloned();
// Use collection permission if it exists, otherwise use direct permission
// Determine effective permission (prioritizing collection over direct)
let mut effective_permission = match collection_permission {
Some(collection) => Some(collection),
None => direct_permission,
};
// Check if the file is publicly accessible and its expiry date hasn't passed
// We still need this check for other potential uses, but don't grant permission based on it here.
let is_public = dashboard_file.publicly_accessible
&& dashboard_file
.public_expiry_date
.map_or(true, |expiry| expiry > now);
// If the file is publicly accessible and either no permission exists or it's lower than CanView,
// grant CanView permission
// REMOVED: Logic that automatically granted CanView for public access.
/*
if is_public && (effective_permission.is_none()) {
effective_permission = Some(AssetPermissionRole::CanView);
}
*/
DashboardFileWithPermission {
dashboard_file,
permission: effective_permission,
permission: effective_permission, // Now only reflects direct or collection permission
}
})
.collect();

View File

@ -177,35 +177,17 @@ pub async fn fetch_metric_file_with_permissions(
None => return Ok(None),
};
// Check if the file is publicly accessible
let is_public = is_publicly_accessible(&metric_file).await;
// If collection permission exists, use it; otherwise use direct permission
let mut effective_permission = match collection_permission {
// Determine effective permission (prioritizing collection over direct)
// Dashboard permission is NOT considered here for the base permission level.
// The handler should check dashboard access separately if direct/collection/public checks fail.
let effective_permission = match collection_permission {
Some(collection) => Some(collection),
None => direct_permission,
};
// If the file is publicly accessible and either no permission exists or it's lower than CanView,
// grant CanView permission
if is_public {
if effective_permission.is_none() {
effective_permission = Some(AssetPermissionRole::CanView);
}
}
// If the user has dashboard-based access to this metric file,
// grant CanView permission if they don't already have a higher permission
if let Some(dashboard_role) = dashboard_permission {
effective_permission = match effective_permission {
Some(current_role) => Some(current_role.max(dashboard_role)),
None => Some(dashboard_role),
};
}
Ok(Some(MetricFileWithPermission {
metric_file,
permission: effective_permission,
permission: effective_permission, // Now only reflects direct or collection permission
}))
}
@ -311,36 +293,40 @@ pub async fn fetch_metric_files_with_permissions(
let collection_permission = collection_permission_map.get(&metric_file.id).cloned();
let dashboard_permission = dashboard_permission_map.get(&metric_file.id).cloned();
// Use collection permission if it exists, otherwise use direct permission
// Determine effective permission (prioritizing collection over direct)
// Dashboard permission is NOT considered here for the base permission level.
let mut effective_permission = match collection_permission {
Some(collection) => Some(collection),
None => direct_permission,
};
// Check if the file is publicly accessible and its expiry date hasn't passed
// We still need this check for other potential uses, but don't grant permission based on it here.
let is_public = metric_file.publicly_accessible
&& metric_file
.public_expiry_date
.map_or(true, |expiry| expiry > now);
// If the file is publicly accessible and either no permission exists or it's lower than CanView,
// grant CanView permission
// REMOVED: Logic that automatically granted CanView for public access.
/*
if is_public && (effective_permission.is_none()) {
effective_permission = Some(AssetPermissionRole::CanView);
}
*/
// If the user has dashboard-based access to this metric file,
// grant CanView permission if they don't already have a higher permission
// REMOVED: Logic that granted CanView based on dashboard access.
/*
if let Some(dashboard_role) = dashboard_permission {
effective_permission = match effective_permission {
Some(current_role) => Some(current_role.max(dashboard_role)),
None => Some(dashboard_role),
};
}
*/
MetricFileWithPermission {
metric_file,
permission: effective_permission,
permission: effective_permission, // Now only reflects direct or collection permission
}
})
.collect();

View File

@ -1,5 +1,5 @@
pub mod metric_files;
pub mod dashboard_files;
pub mod collections;
pub mod dashboard_files;
pub mod metric_files;
pub mod chats;
pub mod organization;

View File

@ -154,13 +154,29 @@ impl TestDb {
/// Insert a test asset permission
pub async fn insert_test_permission(
_conn: &mut diesel_async::pooled_connection::bb8::PooledConnection<'_, diesel_async::AsyncPgConnection>,
_permission: &AssetPermission,
conn: &mut diesel_async::pooled_connection::bb8::PooledConnection<'_, diesel_async::AsyncPgConnection>,
permission: &AssetPermission,
) -> Result<()> {
// In a real test, we would insert the permission into the database
// However, for these tests, we'll skip actual DB operations to avoid foreign key constraints
// and just simulate the operations
println!("Simulated inserting permission");
use database::schema::asset_permissions;
diesel::insert_into(asset_permissions::table)
.values(permission)
.on_conflict((
asset_permissions::identity_id,
asset_permissions::asset_id,
asset_permissions::asset_type,
asset_permissions::identity_type,
))
.do_update()
.set((
asset_permissions::role.eq(permission.role),
asset_permissions::updated_at.eq(permission.updated_at),
asset_permissions::updated_by.eq(permission.updated_by),
asset_permissions::deleted_at.eq::<Option<chrono::DateTime<Utc>>>(None), // Ensure not deleted on update
))
.execute(conn)
.await?;
Ok(())
}

View File

@ -80,20 +80,29 @@ pub async fn get_dashboard_handler(
password: Option<String>,
) -> Result<BusterDashboardResponse> {
// First check if the user has permission to view this dashboard
let dashboard_with_permission =
let dashboard_with_permission_option =
fetch_dashboard_file_with_permission(dashboard_id, &user.id).await?;
// If dashboard not found, return error
let dashboard_with_permission = match dashboard_with_permission {
let dashboard_with_permission = match dashboard_with_permission_option {
Some(dwp) => dwp,
None => return Err(anyhow!("Dashboard not found")),
None => {
tracing::warn!(dashboard_id = %dashboard_id, "Dashboard file not found during fetch");
return Err(anyhow!("Dashboard not found"))
},
};
let dashboard_file = dashboard_with_permission.dashboard_file;
let direct_permission_level = dashboard_with_permission.permission;
// Check if user has proper permission to view the dashboard
let has_direct_permission = check_permission_access(
dashboard_with_permission.permission,
let permission: AssetPermissionRole;
tracing::debug!(dashboard_id = %dashboard_id, user_id = %user.id, "Checking permissions for dashboard");
// Check for direct/admin permission first
tracing::debug!(dashboard_id = %dashboard_id, "Checking direct/admin permissions first.");
let has_sufficient_direct_permission = check_permission_access(
direct_permission_level,
&[
AssetPermissionRole::CanView,
AssetPermissionRole::CanEdit,
@ -103,43 +112,58 @@ pub async fn get_dashboard_handler(
dashboard_file.organization_id,
&user.organizations,
);
// If the user doesn't have direct permission, check if the asset is publicly accessible
if !has_direct_permission {
// Not publicly accessible, so they don't have permission
tracing::debug!(dashboard_id = %dashboard_id, ?direct_permission_level, has_sufficient_direct_permission, "Direct permission check result");
if has_sufficient_direct_permission {
// User has direct/admin permission, use that role
permission = direct_permission_level.unwrap_or(AssetPermissionRole::CanView); // Default just in case
tracing::debug!(dashboard_id = %dashboard_id, user_id = %user.id, ?permission, "Granting access via direct/admin permission.");
} else {
// No sufficient direct/admin permission, check public access rules
tracing::debug!(dashboard_id = %dashboard_id, "Insufficient direct/admin permission. Checking public access rules.");
if !dashboard_file.publicly_accessible {
tracing::warn!(dashboard_id = %dashboard_id, user_id = %user.id, "Permission denied (not public, insufficient direct permission).");
return Err(anyhow!("You don't have permission to view this dashboard"));
}
tracing::debug!(dashboard_id = %dashboard_id, "Dashboard is publicly accessible.");
// Check if the public access has expired
if let Some(expiry_date) = dashboard_file.public_expiry_date {
tracing::debug!(dashboard_id = %dashboard_id, ?expiry_date, "Checking expiry date");
if expiry_date < chrono::Utc::now() {
tracing::warn!(dashboard_id = %dashboard_id, "Public access expired");
return Err(anyhow!("Public access to this dashboard has expired"));
}
}
// Check if a password is required and provided correctly
// Check if a password is required
tracing::debug!(dashboard_id = %dashboard_id, has_password = dashboard_file.public_password.is_some(), "Checking password requirement");
if let Some(required_password) = &dashboard_file.public_password {
tracing::debug!(dashboard_id = %dashboard_id, "Password required. Checking provided password.");
match password {
Some(provided_password) => {
if provided_password != *required_password {
// Incorrect password provided
tracing::warn!(dashboard_id = %dashboard_id, user_id = %user.id, "Incorrect public password provided");
return Err(anyhow!("Incorrect password for public access"));
}
// Password is correct, continue
// Correct password provided, grant CanView via public access
tracing::debug!(dashboard_id = %dashboard_id, user_id = %user.id, "Correct public password provided. Granting CanView.");
permission = AssetPermissionRole::CanView;
}
None => {
// Password is required but not provided
// Password required but none provided
tracing::warn!(dashboard_id = %dashboard_id, user_id = %user.id, "Public password required but none provided");
return Err(anyhow!("public_password required for this dashboard"));
}
}
} else {
// Publicly accessible, not expired, and no password required
tracing::debug!(dashboard_id = %dashboard_id, "Public access granted (no password required).");
permission = AssetPermissionRole::CanView;
}
}
// 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)),

View File

@ -22,6 +22,7 @@ pub struct GetMetricDataRequest {
pub metric_id: Uuid,
pub version_number: Option<i32>,
pub limit: Option<i64>,
pub password: Option<String>,
}
/// Structure for the metric data response
@ -44,7 +45,12 @@ pub async fn get_metric_data_handler(
);
// Retrieve the metric definition based on version, if none, use latest.
let metric = get_metric_handler(&request.metric_id, &user, request.version_number, None).await?;
let metric = get_metric_handler(
&request.metric_id,
&user,
request.version_number,
request.password
).await?;
// Parse the metric definition from YAML to get SQL and dataset IDs
let metric_yml = serde_yaml::from_str::<MetricYml>(&metric.file)?;

View File

@ -85,21 +85,28 @@ pub async fn get_metric_handler(
password: Option<String>,
) -> Result<BusterMetric> {
// 1. Fetch metric file with permission
let metric_file_with_permission = fetch_metric_file_with_permissions(metric_id, &user.id)
let metric_file_with_permission_option = fetch_metric_file_with_permissions(metric_id, &user.id)
.await
.map_err(|e| anyhow!("Failed to fetch metric file with permissions: {}", e))?;
let metric_file_with_permission = if let Some(metric_file) = metric_file_with_permission {
metric_file
let metric_file_with_permission = if let Some(mf) = metric_file_with_permission_option {
mf
} else {
tracing::warn!(metric_id = %metric_id, "Metric file not found during fetch");
return Err(anyhow!("Metric file not found"));
};
let metric_file = metric_file_with_permission.metric_file;
let direct_permission_level = metric_file_with_permission.permission;
// 2. Check if user has proper permission to view the metric
let has_direct_permission = check_permission_access(
metric_file_with_permission.permission,
// 2. Determine the user's access level and enforce access rules
let permission: AssetPermissionRole;
tracing::debug!(metric_id = %metric_id, user_id = %user.id, "Checking permissions for metric");
// Check for direct/admin permission first
tracing::debug!(metric_id = %metric_id, "Checking direct/admin permissions first.");
let has_sufficient_direct_permission = check_permission_access(
direct_permission_level,
&[
AssetPermissionRole::FullAccess,
AssetPermissionRole::Owner,
@ -109,43 +116,58 @@ pub async fn get_metric_handler(
metric_file.organization_id,
&user.organizations,
);
// If the user doesn't have direct permission, check if the asset is publicly accessible
if !has_direct_permission {
// Not publicly accessible, so they don't have permission
tracing::debug!(metric_id = %metric_id, ?direct_permission_level, has_sufficient_direct_permission, "Direct permission check result");
if has_sufficient_direct_permission {
// User has direct/admin permission, use that role
permission = direct_permission_level.unwrap_or(AssetPermissionRole::CanView); // Default just in case
tracing::debug!(metric_id = %metric_id, user_id = %user.id, ?permission, "Granting access via direct/admin permission.");
} else {
// No sufficient direct/admin permission, check public access rules
tracing::debug!(metric_id = %metric_id, "Insufficient direct/admin permission. Checking public access rules.");
if !metric_file.publicly_accessible {
tracing::warn!(metric_id = %metric_id, user_id = %user.id, "Permission denied (not public, insufficient direct permission).");
return Err(anyhow!("You don't have permission to view this metric"));
}
tracing::debug!(metric_id = %metric_id, "Metric is publicly accessible.");
// Check if the public access has expired
if let Some(expiry_date) = metric_file.public_expiry_date {
tracing::debug!(metric_id = %metric_id, ?expiry_date, "Checking expiry date");
if expiry_date < chrono::Utc::now() {
tracing::warn!(metric_id = %metric_id, "Public access expired");
return Err(anyhow!("Public access to this metric has expired"));
}
}
// Check if a password is required and provided correctly
// Check if a password is required
tracing::debug!(metric_id = %metric_id, has_password = metric_file.public_password.is_some(), "Checking password requirement");
if let Some(required_password) = &metric_file.public_password {
tracing::debug!(metric_id = %metric_id, "Password required. Checking provided password.");
match password {
Some(provided_password) => {
if provided_password != *required_password {
// Incorrect password provided
tracing::warn!(metric_id = %metric_id, user_id = %user.id, "Incorrect public password provided");
return Err(anyhow!("Incorrect password for public access"));
}
// Password is correct, continue
// Correct password provided, grant CanView via public access
tracing::debug!(metric_id = %metric_id, user_id = %user.id, "Correct public password provided. Granting CanView.");
permission = AssetPermissionRole::CanView;
}
None => {
// Password is required but not provided
// Password required but none provided
tracing::warn!(metric_id = %metric_id, user_id = %user.id, "Public password required but none provided");
return Err(anyhow!("public_password required for this metric"));
}
}
} else {
// Publicly accessible, not expired, and no password required
tracing::debug!(metric_id = %metric_id, "Public access granted (no password required).");
permission = AssetPermissionRole::CanView;
}
}
// 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);
// Map evaluation score to High/Moderate/Low
let evaluation_score = metric_file.evaluation_score.map(|score| {
if score >= 0.8 {

View File

@ -0,0 +1,234 @@
use anyhow::{Result, Context};
use chrono::Utc;
use database::enums::{AssetPermissionRole, AssetType};
use database::test_utils::{TestDb, cleanup_test_data, insert_test_dashboard_file, insert_test_permission};
use handlers::dashboards::get_dashboard_handler;
use middleware::{AuthenticatedUser, OrganizationMembership};
use serde_json;
use uuid::Uuid;
// Helper to create a basic AuthenticatedUser for tests
fn create_test_auth_user(user_id: Uuid, organization_id: Option<Uuid>) -> AuthenticatedUser {
let organizations = if let Some(org_id) = organization_id {
vec![OrganizationMembership { id: org_id, role: database::enums::UserOrganizationRole::Viewer }] // Default to Viewer
} else {
vec![]
};
AuthenticatedUser {
id: user_id,
organizations,
email: format!("{}@test.com", user_id),
name: Some("Test User".to_string()),
config: serde_json::Value::Null,
created_at: Utc::now(),
updated_at: Utc::now(),
attributes: serde_json::Value::Null,
avatar_url: None,
teams: vec![],
}
}
#[tokio::test]
async fn test_get_dashboard_no_permission_private() -> Result<()> {
let test_db = TestDb::new().await?;
let mut conn = test_db.get_conn().await?;
let owner = test_db.create_test_user().await?;
let dashboard = test_db.create_test_dashboard_file(&owner.id).await?;
insert_test_dashboard_file(&mut conn, &dashboard).await?;
let random_user = create_test_auth_user(Uuid::new_v4(), None); // User not in org, no share
let result = get_dashboard_handler(&dashboard.id, &random_user, None, None).await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("don't have permission"));
cleanup_test_data(&mut conn, &[dashboard.id]).await?;
Ok(())
}
#[tokio::test]
async fn test_get_dashboard_no_permission_public_no_password() -> Result<()> {
let test_db = TestDb::new().await?;
let mut conn = test_db.get_conn().await?;
let owner = test_db.create_test_user().await?;
let mut dashboard = test_db.create_test_dashboard_file(&owner.id).await?;
dashboard.publicly_accessible = true;
insert_test_dashboard_file(&mut conn, &dashboard).await?;
let random_user = create_test_auth_user(Uuid::new_v4(), None);
let result = get_dashboard_handler(&dashboard.id, &random_user, None, None).await;
assert!(result.is_ok());
let response = result.unwrap();
assert_eq!(response.permission, AssetPermissionRole::CanView);
cleanup_test_data(&mut conn, &[dashboard.id]).await?;
Ok(())
}
#[tokio::test]
async fn test_get_dashboard_no_permission_public_correct_password() -> Result<()> {
let test_db = TestDb::new().await?;
let mut conn = test_db.get_conn().await?;
let owner = test_db.create_test_user().await?;
let mut dashboard = test_db.create_test_dashboard_file(&owner.id).await?;
dashboard.publicly_accessible = true;
let password = "testpassword".to_string();
dashboard.public_password = Some(password.clone());
insert_test_dashboard_file(&mut conn, &dashboard).await?;
let random_user = create_test_auth_user(Uuid::new_v4(), None);
let result = get_dashboard_handler(&dashboard.id, &random_user, None, Some(password)).await;
assert!(result.is_ok());
let response = result.unwrap();
assert_eq!(response.permission, AssetPermissionRole::CanView);
cleanup_test_data(&mut conn, &[dashboard.id]).await?;
Ok(())
}
#[tokio::test]
async fn test_get_dashboard_no_permission_public_incorrect_password() -> Result<()> {
let test_db = TestDb::new().await?;
let mut conn = test_db.get_conn().await?;
let owner = test_db.create_test_user().await?;
let mut dashboard = test_db.create_test_dashboard_file(&owner.id).await?;
dashboard.publicly_accessible = true;
dashboard.public_password = Some("correctpassword".to_string());
insert_test_dashboard_file(&mut conn, &dashboard).await?;
let random_user = create_test_auth_user(Uuid::new_v4(), None);
let result = get_dashboard_handler(&dashboard.id, &random_user, None, Some("wrongpassword".to_string())).await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Incorrect password"));
cleanup_test_data(&mut conn, &[dashboard.id]).await?;
Ok(())
}
#[tokio::test]
async fn test_get_dashboard_no_permission_public_missing_password() -> Result<()> {
let test_db = TestDb::new().await?;
let mut conn = test_db.get_conn().await?;
let owner = test_db.create_test_user().await?;
let mut dashboard = test_db.create_test_dashboard_file(&owner.id).await?;
dashboard.publicly_accessible = true;
dashboard.public_password = Some("correctpassword".to_string());
insert_test_dashboard_file(&mut conn, &dashboard).await?;
let random_user = create_test_auth_user(Uuid::new_v4(), None);
let result = get_dashboard_handler(&dashboard.id, &random_user, None, None).await; // No password
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("public_password required"));
cleanup_test_data(&mut conn, &[dashboard.id]).await?;
Ok(())
}
#[tokio::test]
async fn test_get_dashboard_no_permission_public_expired() -> Result<()> {
let test_db = TestDb::new().await?;
let mut conn = test_db.get_conn().await?;
let owner = test_db.create_test_user().await?;
let mut dashboard = test_db.create_test_dashboard_file(&owner.id).await?;
dashboard.publicly_accessible = true;
dashboard.public_expiry_date = Some(Utc::now() - chrono::Duration::days(1)); // Expired
insert_test_dashboard_file(&mut conn, &dashboard).await?;
let random_user = create_test_auth_user(Uuid::new_v4(), None);
let result = get_dashboard_handler(&dashboard.id, &random_user, None, None).await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("expired"));
cleanup_test_data(&mut conn, &[dashboard.id]).await?;
Ok(())
}
#[tokio::test]
async fn test_get_dashboard_direct_permission_public_password() -> Result<()> {
// User has direct CanEdit permission, should bypass public password check
let test_db = TestDb::new().await?;
let mut conn = test_db.get_conn().await?;
let user = test_db.create_test_user().await?;
let mut dashboard = test_db.create_test_dashboard_file(&user.id).await?;
dashboard.publicly_accessible = true;
dashboard.public_password = Some("testpassword".to_string());
insert_test_dashboard_file(&mut conn, &dashboard).await?;
let permission = test_db.create_asset_permission(&dashboard.id, AssetType::DashboardFile, &user.id, AssetPermissionRole::CanEdit).await?;
insert_test_permission(&mut conn, &permission).await?;
let auth_user = create_test_auth_user(user.id, Some(test_db.organization_id));
let result = get_dashboard_handler(&dashboard.id, &auth_user, None, None).await; // No password
assert!(result.is_ok());
let response = result.unwrap();
assert_eq!(response.permission, AssetPermissionRole::CanEdit);
cleanup_test_data(&mut conn, &[dashboard.id]).await?;
Ok(())
}
#[tokio::test]
async fn test_get_dashboard_admin_role_public_password() -> Result<()> {
// User is WorkspaceAdmin, should bypass public password check
let test_db = TestDb::new().await?;
let mut conn = test_db.get_conn().await?;
let admin_user = test_db.create_test_user().await?;
let mut dashboard = test_db.create_test_dashboard_file(&admin_user.id).await?;
dashboard.publicly_accessible = true;
dashboard.public_password = Some("testpassword".to_string());
insert_test_dashboard_file(&mut conn, &dashboard).await?;
let auth_user = AuthenticatedUser {
id: admin_user.id,
organizations: vec![OrganizationMembership {
id: test_db.organization_id,
role: database::enums::UserOrganizationRole::WorkspaceAdmin,
}],
email: format!("{}@test.com", admin_user.id),
name: Some("Test Admin User".to_string()),
config: serde_json::Value::Null,
created_at: Utc::now(),
updated_at: Utc::now(),
attributes: serde_json::Value::Null,
avatar_url: None,
teams: vec![],
};
let result = get_dashboard_handler(&dashboard.id, &auth_user, None, None).await; // No password
assert!(result.is_ok());
// Admins currently default to CanView if no explicit permission exists on the asset itself
let response = result.unwrap();
assert_eq!(response.permission, AssetPermissionRole::CanView);
cleanup_test_data(&mut conn, &[dashboard.id]).await?;
Ok(())
}
#[tokio::test]
async fn test_get_dashboard_not_found() -> Result<()> {
let test_db = TestDb::new().await?;
let user = test_db.create_test_user().await?;
let auth_user = create_test_auth_user(user.id, None);
let non_existent_id = Uuid::new_v4();
let result = get_dashboard_handler(&non_existent_id, &auth_user, None, None).await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not found"));
Ok(())
}

View File

@ -1,2 +1,2 @@
mod list_sharing_test;
mod permission_field_test;
mod get_dashboard_handler_permission_test;
mod permission_field_test;

View File

@ -0,0 +1,266 @@
use anyhow::{Context, Result};
use chrono::Utc;
use database::enums::{AssetPermissionRole, AssetType};
use database::test_utils::{
cleanup_test_data, insert_test_metric_file, insert_test_permission, TestDb,
};
use handlers::metrics::get_metric_handler;
use middleware::{AuthenticatedUser, OrganizationMembership}; // Assuming AuthenticatedUser needs Org info
use serde_json;
use uuid::Uuid;
// Helper to create a basic AuthenticatedUser for tests
fn create_test_auth_user(user_id: Uuid, organization_id: Option<Uuid>) -> AuthenticatedUser {
let organizations = if let Some(org_id) = organization_id {
vec![OrganizationMembership {
id: org_id,
role: database::enums::UserOrganizationRole::Viewer,
}] // Default to Viewer for simplicity
} else {
vec![]
};
AuthenticatedUser {
id: user_id,
organizations,
email: format!("{}@test.com", user_id),
name: Some("Test User".to_string()),
config: serde_json::Value::Null,
created_at: Utc::now(),
updated_at: Utc::now(),
attributes: serde_json::Value::Null,
avatar_url: None,
teams: vec![],
}
}
// --- Test Functions Will Go Here ---
#[tokio::test]
async fn test_get_metric_no_permission_private() -> Result<()> {
let test_db = TestDb::new().await?;
let mut conn = test_db.get_conn().await?;
let owner = test_db.create_test_user().await?;
let metric = test_db.create_test_metric_file(&owner.id).await?;
insert_test_metric_file(&mut conn, &metric).await?;
let random_user = create_test_auth_user(Uuid::new_v4(), None); // User not in org, no share
let result = get_metric_handler(&metric.id, &random_user, None, None).await;
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("don't have permission"));
cleanup_test_data(&mut conn, &[metric.id]).await?;
Ok(())
}
#[tokio::test]
async fn test_get_metric_no_permission_public_no_password() -> Result<()> {
let test_db = TestDb::new().await?;
let mut conn = test_db.get_conn().await?;
let owner = test_db.create_test_user().await?;
let mut metric = test_db.create_test_metric_file(&owner.id).await?;
metric.publicly_accessible = true;
insert_test_metric_file(&mut conn, &metric).await?;
let random_user = create_test_auth_user(Uuid::new_v4(), None); // User not in org, no share
let result = get_metric_handler(&metric.id, &random_user, None, None).await;
assert!(result.is_ok());
let response = result.unwrap();
assert_eq!(response.permission, AssetPermissionRole::CanView);
cleanup_test_data(&mut conn, &[metric.id]).await?;
Ok(())
}
#[tokio::test]
async fn test_get_metric_no_permission_public_correct_password() -> Result<()> {
let test_db = TestDb::new().await?;
let mut conn = test_db.get_conn().await?;
let owner = test_db.create_test_user().await?;
let mut metric = test_db.create_test_metric_file(&owner.id).await?;
metric.publicly_accessible = true;
let password = "testpassword".to_string();
metric.public_password = Some(password.clone());
insert_test_metric_file(&mut conn, &metric).await?;
let random_user = create_test_auth_user(Uuid::new_v4(), None); // User not in org, no share
let result = get_metric_handler(&metric.id, &random_user, None, Some(password)).await;
assert!(result.is_ok());
let response = result.unwrap();
assert_eq!(response.permission, AssetPermissionRole::CanView);
cleanup_test_data(&mut conn, &[metric.id]).await?;
Ok(())
}
#[tokio::test]
async fn test_get_metric_no_permission_public_incorrect_password() -> Result<()> {
let test_db = TestDb::new().await?;
let mut conn = test_db.get_conn().await?;
let owner = test_db.create_test_user().await?;
let mut metric = test_db.create_test_metric_file(&owner.id).await?;
metric.publicly_accessible = true;
metric.public_password = Some("correctpassword".to_string());
insert_test_metric_file(&mut conn, &metric).await?;
let random_user = create_test_auth_user(Uuid::new_v4(), None); // User not in org, no share
let result = get_metric_handler(
&metric.id,
&random_user,
None,
Some("wrongpassword".to_string()),
)
.await;
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Incorrect password"));
cleanup_test_data(&mut conn, &[metric.id]).await?;
Ok(())
}
#[tokio::test]
async fn test_get_metric_no_permission_public_missing_password() -> Result<()> {
let test_db = TestDb::new().await?;
let mut conn = test_db.get_conn().await?;
let owner = test_db.create_test_user().await?;
let mut metric = test_db.create_test_metric_file(&owner.id).await?;
metric.publicly_accessible = true;
metric.public_password = Some("correctpassword".to_string());
insert_test_metric_file(&mut conn, &metric).await?;
let random_user = create_test_auth_user(Uuid::new_v4(), None); // User not in org, no share
let result = get_metric_handler(&metric.id, &random_user, None, None).await; // No password provided
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("public_password required"));
cleanup_test_data(&mut conn, &[metric.id]).await?;
Ok(())
}
#[tokio::test]
async fn test_get_metric_no_permission_public_expired() -> Result<()> {
let test_db = TestDb::new().await?;
let mut conn = test_db.get_conn().await?;
let owner = test_db.create_test_user().await?;
let mut metric = test_db.create_test_metric_file(&owner.id).await?;
metric.publicly_accessible = true;
metric.public_expiry_date = Some(Utc::now() - chrono::Duration::days(1)); // Expired yesterday
insert_test_metric_file(&mut conn, &metric).await?;
let random_user = create_test_auth_user(Uuid::new_v4(), None); // User not in org, no share
let result = get_metric_handler(&metric.id, &random_user, None, None).await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("expired"));
cleanup_test_data(&mut conn, &[metric.id]).await?;
Ok(())
}
#[tokio::test]
async fn test_get_metric_direct_permission_public_password() -> Result<()> {
// User has direct CanEdit permission, should bypass public password check
let test_db = TestDb::new().await?;
let mut conn = test_db.get_conn().await?;
let user = test_db.create_test_user().await?;
let mut metric = test_db.create_test_metric_file(&user.id).await?;
metric.publicly_accessible = true;
metric.public_password = Some("testpassword".to_string());
insert_test_metric_file(&mut conn, &metric).await?;
let permission = test_db
.create_asset_permission(
&metric.id,
AssetType::MetricFile,
&user.id,
AssetPermissionRole::CanEdit,
)
.await?;
insert_test_permission(&mut conn, &permission).await?;
let auth_user = create_test_auth_user(user.id, Some(test_db.organization_id));
let result = get_metric_handler(&metric.id, &auth_user, None, None).await; // No password provided
assert!(result.is_ok());
let response = result.unwrap();
assert_eq!(response.permission, AssetPermissionRole::CanEdit);
cleanup_test_data(&mut conn, &[metric.id]).await?;
Ok(())
}
#[tokio::test]
async fn test_get_metric_admin_role_public_password() -> Result<()> {
// User is WorkspaceAdmin, should bypass public password check
let test_db = TestDb::new().await?;
let mut conn = test_db.get_conn().await?;
let admin_user = test_db.create_test_user().await?;
let mut metric = test_db.create_test_metric_file(&admin_user.id).await?;
metric.publicly_accessible = true;
metric.public_password = Some("testpassword".to_string());
insert_test_metric_file(&mut conn, &metric).await?;
// Create AuthenticatedUser with Admin role and all fields
let auth_user = AuthenticatedUser {
id: admin_user.id,
organizations: vec![OrganizationMembership {
id: test_db.organization_id,
role: database::enums::UserOrganizationRole::WorkspaceAdmin,
}],
email: format!("{}@test.com", admin_user.id),
name: Some("Test Admin User".to_string()),
config: serde_json::Value::Null,
created_at: Utc::now(),
updated_at: Utc::now(),
attributes: serde_json::Value::Null,
avatar_url: None,
teams: vec![],
};
let result = get_metric_handler(&metric.id, &auth_user, None, None).await; // No password provided
assert!(result.is_ok());
// Admins currently default to CanView if no explicit permission exists on the asset itself,
// even though check_permission_access returns true. This might be desired or not.
// Let's assert CanView for now, reflecting current check_permission_access behavior combined with handler logic.
let response = result.unwrap();
assert_eq!(response.permission, AssetPermissionRole::CanView);
cleanup_test_data(&mut conn, &[metric.id]).await?;
Ok(())
}
#[tokio::test]
async fn test_get_metric_not_found() -> Result<()> {
let test_db = TestDb::new().await?;
let user = test_db.create_test_user().await?;
let auth_user = create_test_auth_user(user.id, None);
let non_existent_id = Uuid::new_v4();
let result = get_metric_handler(&non_existent_id, &auth_user, None, None).await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not found"));
Ok(())
}

View File

@ -1,3 +1,4 @@
// Test modules
pub mod update_metric_test;
pub mod permission_field_test;
pub mod get_metric_handler_permission_test;

View File

@ -11,13 +11,14 @@ use uuid::Uuid;
pub struct GetMetricDataParams {
pub version_number: Option<i32>,
pub limit: Option<i64>,
pub password: Option<String>,
}
pub async fn get_metric_data_rest_handler(
Extension(user): Extension<AuthenticatedUser>,
Path(metric_id): Path<Uuid>,
Query(params): Query<GetMetricDataParams>,
) -> Result<ApiResponse<MetricDataResponse>, (StatusCode, &'static str)> {
) -> Result<ApiResponse<MetricDataResponse>, (StatusCode, String)> {
tracing::info!(
"Processing GET request for metric data with ID: {}",
metric_id
@ -27,16 +28,25 @@ pub async fn get_metric_data_rest_handler(
metric_id,
version_number: params.version_number,
limit: params.limit,
password: params.password,
};
match handlers::metrics::get_metric_data_handler(request, user).await {
Ok(response) => Ok(ApiResponse::JsonData(response)),
Err(e) => {
tracing::error!("Error getting metric data: {}", e);
Err((
StatusCode::INTERNAL_SERVER_ERROR,
"Failed to get metric data",
))
let error_message = e.to_string();
tracing::error!("Error getting metric data: {}", error_message);
// Check for specific password-related errors
if error_message.contains("Incorrect password") || error_message.contains("public_password required") {
Err((StatusCode::IM_A_TEAPOT, error_message))
} else if error_message.contains("don't have permission") || error_message.contains("not found") || error_message.contains("expired") {
// Handle permission, not found, or expired errors with 403 Forbidden
Err((StatusCode::FORBIDDEN, error_message))
} else {
// Default to 500 for other errors
Err((StatusCode::INTERNAL_SERVER_ERROR, error_message))
}
}
}
}