mirror of https://github.com/buster-so/buster.git
9.2 KiB
9.2 KiB
API Metrics Sharing - Delete Endpoint PRD
Problem Statement
Users need the ability to remove sharing permissions for metrics through a REST API endpoint.
Technical Design
Endpoint Specification
- Method: DELETE
- Path: /metrics/:id/sharing
- Description: Removes sharing permissions for a metric
- Authentication: Required
- Authorization: User must have Owner or FullAccess permission for the metric
Request Structure
#[derive(Debug, Deserialize)]
pub struct DeleteSharingRequest {
pub emails: Vec<String>,
}
Response Structure
// Success response is a simple message
// Error responses include appropriate status codes and error messages
Implementation Details
New Files ✅
/src/routes/rest/routes/metrics/sharing/delete_sharing.rs
- REST handler for deleting sharing permissions ✅/libs/handlers/src/metrics/sharing/delete_sharing_handler.rs
- Business logic for deleting sharing permissions ✅
REST Handler Implementation ✅
// delete_sharing.rs
pub async fn delete_metric_sharing_rest_handler(
Extension(user): Extension<AuthenticatedUser>,
Path(id): Path<Uuid>,
Json(request): Json<DeleteSharingRequest>,
) -> Result<ApiResponse<String>, (StatusCode, String)> {
tracing::info!("Processing DELETE request for metric sharing with ID: {}, user_id: {}", id, user.id);
match delete_metric_sharing_handler(&id, &user.id, request.emails).await {
Ok(_) => Ok(ApiResponse::Success("Sharing permissions deleted successfully".to_string())),
Err(e) => {
tracing::error!("Error deleting 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 delete sharing permissions: {}", e)))
}
}
}
Handler Implementation ✅
// delete_sharing_handler.rs
pub async fn delete_metric_sharing_handler(
metric_id: &Uuid,
user_id: &Uuid,
emails: Vec<String>,
) -> 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 delete sharing for 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 delete sharing for this metric"));
}
// 3. Process each email and delete sharing permissions
for email in emails {
// The remove_share_by_email function handles soft deletion of permissions
match remove_share_by_email(
&email,
*metric_id,
AssetType::MetricFile,
*user_id,
).await {
Ok(_) => {
tracing::info!("Deleted sharing permission for email: {} on metric: {}", email, metric_id);
},
Err(e) => {
// If the error is because the permission doesn't exist, we can ignore it
if e.to_string().contains("No active permission found") {
tracing::warn!("No active permission found for email {}: {}", email, e);
continue;
}
tracing::error!("Failed to delete sharing for email {}: {}", email, e);
return Err(anyhow!("Failed to delete sharing for email {}: {}", email, e));
}
}
}
Ok(())
}
Sharing Library Integration ✅
This endpoint leverages the following functions from the sharing library:
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 delete sharing for the metric.
remove_share_by_email
from@[api/libs/sharing/src]/remove_asset_permissions.rs
:
pub async fn remove_share_by_email(
email: &str,
asset_id: Uuid,
asset_type: AssetType,
updated_by: Uuid,
) -> Result<()>
This function handles the soft deletion of permissions by email. Here's how it works internally:
// From the implementation of remove_share_by_email
pub async fn remove_share_by_email(
email: &str,
asset_id: Uuid,
asset_type: AssetType,
updated_by: Uuid,
) -> Result<()> {
// Validate email format
if !email.contains('@') {
return Err(SharingError::InvalidEmail(email.to_string()).into());
}
// Find the user by email
let user = match find_user_by_email(email).await? {
Some(user) => user,
None => return Err(SharingError::UserNotFound(email.to_string()).into()),
};
// Remove the permission
remove_share(
user.id,
IdentityType::User,
asset_id,
asset_type,
updated_by,
)
.await
}
find_user_by_email
from@[api/libs/sharing/src]/user_lookup.rs
(used internally byremove_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.
Soft Deletion Mechanism ✅
The remove_share_by_email
function performs a soft deletion by updating the deleted_at
field in the database:
// From the implementation of remove_share
// Soft delete - update the deleted_at field
let rows = diesel::update(asset_permissions::table)
.filter(asset_permissions::identity_id.eq(identity_id))
.filter(asset_permissions::identity_type.eq(identity_type))
.filter(asset_permissions::asset_id.eq(asset_id))
.filter(asset_permissions::asset_type.eq(asset_type))
.filter(asset_permissions::deleted_at.is_null())
.set((
asset_permissions::deleted_at.eq(now),
asset_permissions::updated_at.eq(now),
asset_permissions::updated_by.eq(updated_by),
))
.execute(&mut conn)
.await
.context("Failed to remove asset permission")?;
This approach ensures that:
- The permission record is preserved for audit purposes
- The permission can be restored if needed
- The permission won't be included in queries that filter for active permissions
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 delete sharing for the metric
- 400 Bad Request - For invalid email addresses
- 500 Internal Server Error - For database errors or other unexpected issues
Input Validation ✅
- Email addresses must be properly formatted (contains '@')
- 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 deletions
Integration Tests ✅
- Test DELETE /metrics/:id/sharing with valid ID, authorized user, and valid emails
- Test DELETE /metrics/:id/sharing with valid ID, unauthorized user
- Test DELETE /metrics/:id/sharing with non-existent metric ID
- Test DELETE /metrics/:id/sharing with invalid email formats
- Test DELETE /metrics/:id/sharing with non-existent user emails
Test Cases ✅
- Should delete sharing permissions for valid emails
- Should return 403 when user doesn't have Owner or FullAccess permission
- Should return 404 when metric doesn't exist
- Should return 400 when email is invalid
- Should not error when trying to delete a non-existent permission
Performance Considerations
- For bulk deletions with many emails, consider implementing a background job for processing
- Monitor database performance for large batches of delete operations
Security Considerations
- Ensure that only users with Owner or FullAccess permission can delete sharing
- Validate email addresses to prevent injection attacks
- Implement rate limiting to prevent abuse
- Consider adding additional checks to prevent users from removing their own Owner access
Monitoring
- Log all requests with appropriate context
- Track performance metrics for the endpoint
- Monitor error rates and types
- Track sharing deletion operations by user for audit purposes