mirror of https://github.com/buster-so/buster.git
ok password finally working
This commit is contained in:
parent
d346bca850
commit
f5678c4a4d
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
|
@ -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(())
|
||||
}
|
||||
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(())
|
||||
}
|
|
@ -1,2 +1,2 @@
|
|||
mod list_sharing_test;
|
||||
mod permission_field_test;
|
||||
mod get_dashboard_handler_permission_test;
|
||||
mod permission_field_test;
|
||||
|
|
|
@ -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(())
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
// Test modules
|
||||
pub mod update_metric_test;
|
||||
pub mod permission_field_test;
|
||||
pub mod get_metric_handler_permission_test;
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue