mirror of https://github.com/buster-so/buster.git
229 lines
8.4 KiB
Markdown
229 lines
8.4 KiB
Markdown
|
# API Metrics Sharing - Update Endpoint PRD
|
||
|
|
||
|
## Problem Statement
|
||
|
Users need the ability to update sharing permissions for metrics through a REST API endpoint.
|
||
|
|
||
|
## Technical Design
|
||
|
|
||
|
### Endpoint Specification
|
||
|
- **Method**: PUT
|
||
|
- **Path**: /metrics/:id/sharing
|
||
|
- **Description**: Updates sharing permissions for a metric
|
||
|
- **Authentication**: Required
|
||
|
- **Authorization**: User must have Owner or FullAccess permission for the metric
|
||
|
|
||
|
### Request Structure
|
||
|
```rust
|
||
|
#[derive(Debug, Deserialize)]
|
||
|
pub struct SharingRequest {
|
||
|
pub emails: Vec<String>,
|
||
|
pub role: AssetPermissionRole,
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### Response Structure
|
||
|
```rust
|
||
|
// 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/update_sharing.rs` - REST handler for updating sharing permissions
|
||
|
2. `/libs/handlers/src/metrics/sharing/update_sharing_handler.rs` - Business logic for updating sharing permissions
|
||
|
|
||
|
#### REST Handler Implementation
|
||
|
```rust
|
||
|
// update_sharing.rs
|
||
|
pub async fn update_metric_sharing_rest_handler(
|
||
|
Extension(user): Extension<AuthenticatedUser>,
|
||
|
Path(id): Path<Uuid>,
|
||
|
Json(request): Json<SharingRequest>,
|
||
|
) -> Result<ApiResponse<String>, (StatusCode, String)> {
|
||
|
tracing::info!("Processing PUT request for metric sharing with ID: {}, user_id: {}", id, user.id);
|
||
|
|
||
|
match update_metric_sharing_handler(&id, &user.id, request.emails, request.role).await {
|
||
|
Ok(_) => Ok(ApiResponse::Success("Sharing permissions updated successfully".to_string())),
|
||
|
Err(e) => {
|
||
|
tracing::error!("Error updating 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 update sharing permissions: {}", e)))
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
#### Handler Implementation
|
||
|
```rust
|
||
|
// update_sharing_handler.rs
|
||
|
pub async fn update_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 update 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 update sharing for this metric"));
|
||
|
}
|
||
|
|
||
|
// 3. Process each email and update sharing permissions
|
||
|
for email in emails {
|
||
|
// The create_share_by_email function handles both creation and updates
|
||
|
// It performs an upsert operation in the database
|
||
|
match create_share_by_email(
|
||
|
&email,
|
||
|
*metric_id,
|
||
|
AssetType::MetricFile,
|
||
|
role,
|
||
|
*user_id,
|
||
|
).await {
|
||
|
Ok(_) => {
|
||
|
tracing::info!("Updated sharing permission for email: {} on metric: {}", email, metric_id);
|
||
|
},
|
||
|
Err(e) => {
|
||
|
tracing::error!("Failed to update sharing for email {}: {}", email, e);
|
||
|
return Err(anyhow!("Failed to update 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`:
|
||
|
```rust
|
||
|
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 update sharing for the metric.
|
||
|
|
||
|
2. `create_share_by_email` from `@[api/libs/sharing/src]/create_asset_permission.rs`:
|
||
|
```rust
|
||
|
pub async fn create_share_by_email(
|
||
|
email: &str,
|
||
|
asset_id: Uuid,
|
||
|
asset_type: AssetType,
|
||
|
role: AssetPermissionRole,
|
||
|
created_by: Uuid,
|
||
|
) -> Result<AssetPermission>
|
||
|
```
|
||
|
This function is used for both creating and updating permissions. It performs an upsert operation in the database:
|
||
|
|
||
|
```rust
|
||
|
// From the implementation of create_share_by_email
|
||
|
// This shows how it handles the upsert operation
|
||
|
let permission = diesel::insert_into(asset_permissions::table)
|
||
|
.values(&new_permission)
|
||
|
.on_conflict((
|
||
|
asset_permissions::identity_id,
|
||
|
asset_permissions::identity_type,
|
||
|
asset_permissions::asset_id,
|
||
|
asset_permissions::asset_type,
|
||
|
))
|
||
|
.do_update()
|
||
|
.set((
|
||
|
asset_permissions::role.eq(excluded(asset_permissions::role)),
|
||
|
asset_permissions::updated_at.eq(now),
|
||
|
asset_permissions::updated_by.eq(created_by),
|
||
|
asset_permissions::deleted_at.eq(None), // Undelete if previously deleted
|
||
|
))
|
||
|
.get_result::<AssetPermission>(&mut conn)
|
||
|
.await
|
||
|
.context("Failed to create or update asset permission")?;
|
||
|
```
|
||
|
|
||
|
### Key Differences from Create Endpoint
|
||
|
While the update endpoint uses the same `create_share_by_email` function as the create endpoint, there are some key differences in its usage:
|
||
|
|
||
|
1. **Semantic Difference**: The PUT method indicates an update operation, while POST indicates creation.
|
||
|
2. **Expected Behavior**: The update endpoint is expected to modify existing permissions, while the create endpoint is expected to add new ones.
|
||
|
3. **Error Handling**: The update endpoint might handle "permission not found" differently than the create endpoint.
|
||
|
4. **Documentation**: The API documentation will describe these endpoints differently to users.
|
||
|
|
||
|
### 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 update sharing for 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 updates
|
||
|
|
||
|
#### Integration Tests
|
||
|
- Test PUT /metrics/:id/sharing with valid ID, authorized user, and valid emails
|
||
|
- Test PUT /metrics/:id/sharing with valid ID, unauthorized user
|
||
|
- Test PUT /metrics/:id/sharing with non-existent metric ID
|
||
|
- Test PUT /metrics/:id/sharing with invalid email formats
|
||
|
- Test PUT /metrics/:id/sharing with non-existent user emails
|
||
|
- Test PUT /metrics/:id/sharing with invalid roles
|
||
|
|
||
|
#### Test Cases
|
||
|
1. Should update 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 updates with many emails, consider implementing a background job for processing
|
||
|
- Monitor database performance for large batches of update operations
|
||
|
|
||
|
### Security Considerations
|
||
|
- Ensure that only users with Owner or FullAccess permission can update sharing
|
||
|
- 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 update operations by user for audit purposes
|