buster/api/prds/active/api_metrics_sharing_create.md

7.3 KiB

API Metrics Sharing - Create Endpoint PRD

Problem Statement

Users need the ability to share metrics with other users via a REST API endpoint.

Technical Design

Endpoint Specification

  • Method: POST
  • Path: /metrics/:id/sharing
  • Description: Shares a metric with specified users
  • Authentication: Required
  • Authorization: User must have Owner or FullAccess permission for the metric

Request Structure

#[derive(Debug, Deserialize)]
pub struct SharingRequest {
    pub emails: Vec<String>,
    pub role: AssetPermissionRole,
}

Response Structure

// Success response is a simple message
// Error responses include appropriate status codes and error messages

Implementation Details

New Files

  1. /src/routes/rest/routes/metrics/sharing/create_sharing.rs - REST handler for creating sharing permissions
  2. /libs/handlers/src/metrics/sharing/create_sharing_handler.rs - Business logic for creating sharing permissions

REST Handler Implementation

// create_sharing.rs
pub async fn create_metric_sharing_rest_handler(
    Extension(user): Extension<AuthenticatedUser>,
    Path(id): Path<Uuid>,
    Json(request): Json<SharingRequest>,
) -> Result<ApiResponse<String>, (StatusCode, String)> {
    tracing::info!("Processing POST request for metric sharing with ID: {}, user_id: {}", id, user.id);

    match create_metric_sharing_handler(&id, &user.id, request.emails, request.role).await {
        Ok(_) => Ok(ApiResponse::Success("Sharing permissions created successfully".to_string())),
        Err(e) => {
            tracing::error!("Error creating sharing permissions: {}", e);
            
            // Map specific errors to appropriate status codes
            if e.to_string().contains("not found") {
                return Err((StatusCode::NOT_FOUND, format!("Metric not found: {}", e)));
            } else if e.to_string().contains("permission") {
                return Err((StatusCode::FORBIDDEN, format!("Insufficient permissions: {}", e)));
            } else if e.to_string().contains("invalid email") {
                return Err((StatusCode::BAD_REQUEST, format!("Invalid email: {}", e)));
            }
            
            Err((StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to create sharing permissions: {}", e)))
        }
    }
}

Handler Implementation

// create_sharing_handler.rs
pub async fn create_metric_sharing_handler(
    metric_id: &Uuid,
    user_id: &Uuid,
    emails: Vec<String>,
    role: AssetPermissionRole,
) -> Result<()> {
    // 1. Validate the metric exists
    let metric = match get_metric_by_id(metric_id).await {
        Ok(Some(metric)) => metric,
        Ok(None) => return Err(anyhow!("Metric not found")),
        Err(e) => return Err(anyhow!("Error fetching metric: {}", e)),
    };

    // 2. Check if user has permission to share the metric (Owner or FullAccess)
    let has_permission = has_permission(
        *metric_id,
        AssetType::MetricFile,
        *user_id,
        IdentityType::User,
        AssetPermissionRole::FullAccess, // Owner role implicitly has FullAccess permissions
    ).await?;

    if !has_permission {
        return Err(anyhow!("User does not have permission to share this metric"));
    }

    // 3. Process each email and create sharing permissions
    for email in emails {
        // Create or update the permission using create_share_by_email
        match create_share_by_email(
            &email,
            *metric_id,
            AssetType::MetricFile,
            role,
            *user_id,
        ).await {
            Ok(_) => {
                tracing::info!("Created sharing permission for email: {} on metric: {}", email, metric_id);
            },
            Err(e) => {
                tracing::error!("Failed to create sharing for email {}: {}", email, e);
                return Err(anyhow!("Failed to create sharing for email {}: {}", email, e));
            }
        }
    }

    Ok(())
}

Sharing Library Integration

This endpoint leverages the following functions from the sharing library:

  1. has_permission from @[api/libs/sharing/src]/check_asset_permission.rs:
pub async fn has_permission(
    asset_id: Uuid,
    asset_type: AssetType,
    identity_id: Uuid,
    identity_type: IdentityType,
    required_role: AssetPermissionRole,
) -> Result<bool>

This function checks if a user has the required permission level for an asset. It's used to verify that the user has Owner or FullAccess permission to share the metric.

  1. create_share_by_email from @[api/libs/sharing/src]/create_asset_permission.rs:
pub async fn create_share_by_email(
    email: &str,
    asset_id: Uuid,
    asset_type: AssetType,
    role: AssetPermissionRole,
    created_by: Uuid,
) -> Result<AssetPermission>

This function creates or updates an asset permission for a user identified by email. It handles:

  • Email validation
  • User lookup by email
  • Permission creation or update
  • Error handling for invalid emails or non-existent users
  1. find_user_by_email from @[api/libs/sharing/src]/user_lookup.rs (used internally by create_share_by_email):
pub async fn find_user_by_email(email: &str) -> Result<Option<User>>

This function looks up a user by their email address, which is necessary for resolving email addresses to user IDs.

Error Handling

The handler will return appropriate error responses:

  • 404 Not Found - If the metric doesn't exist
  • 403 Forbidden - If the user doesn't have permission to share the metric
  • 400 Bad Request - For invalid email addresses or roles
  • 500 Internal Server Error - For database errors or other unexpected issues

Input Validation

  • Email addresses must be properly formatted (contains '@')
  • Roles must be valid AssetPermissionRole values
  • The metric ID must be a valid UUID

Testing Strategy

Unit Tests

  • Test permission validation logic
  • Test error handling for non-existent metrics
  • Test error handling for unauthorized users
  • Test error handling for invalid emails
  • Test successful sharing creation

Integration Tests

  • Test POST /metrics/:id/sharing with valid ID, authorized user, and valid emails
  • Test POST /metrics/:id/sharing with valid ID, unauthorized user
  • Test POST /metrics/:id/sharing with non-existent metric ID
  • Test POST /metrics/:id/sharing with invalid email formats
  • Test POST /metrics/:id/sharing with non-existent user emails
  • Test POST /metrics/:id/sharing with invalid roles

Test Cases

  1. Should create sharing permissions for valid emails and roles
  2. Should return 403 when user doesn't have Owner or FullAccess permission
  3. Should return 404 when metric doesn't exist
  4. Should return 400 when email is invalid
  5. Should return 400 when role is invalid

Performance Considerations

  • For bulk sharing with many emails, consider implementing a background job for processing
  • Monitor database performance for large batches of sharing operations

Security Considerations

  • Ensure that only users with Owner or FullAccess permission can share metrics
  • Validate email addresses to prevent injection attacks
  • Validate roles to prevent privilege escalation
  • Implement rate limiting to prevent abuse

Monitoring

  • Log all requests with appropriate context
  • Track performance metrics for the endpoint
  • Monitor error rates and types
  • Track sharing operations by user for audit purposes