buster/api/prds/active/refactor_sharing_permission...

131 lines
6.6 KiB
Markdown
Raw Normal View History

2025-04-09 00:29:39 +08:00
# Sub-PRD: Simplified Asset Permission Helper
2025-04-08 23:20:20 +08:00
**Author:** AI Assistant (Pair Programming with User)
**Date:** 2023-10-27
**Status:** Proposed
**Parent PRD:** [Project: Granular Asset Permission Checks](mdc:prds/active/project_granular_asset_permissions.md)
## 1. Overview
2025-04-09 00:29:39 +08:00
This PRD details the creation of a simplified, centralized helper function within the `libs/sharing` crate. This function is primarily intended for contexts (like pre-execution checks) where only an asset's ID is available, and an efficient permission check is needed. It leverages the user's cached organization roles and performs a targeted database query only for direct asset permissions.
2025-04-08 23:20:20 +08:00
## 2. Problem Statement
2025-04-09 00:29:39 +08:00
While handlers retrieving full asset metadata can use efficient fetch helpers (like `fetch_..._with_permissions`), handlers performing actions based only on an asset ID (e.g., executing a query for a metric ID) need a way to check permissions without re-fetching the user's organization roles. A dedicated helper using cached roles and querying only direct permissions is needed for these scenarios.
2025-04-08 23:20:20 +08:00
## 3. Goals
2025-04-09 00:29:39 +08:00
- Create a reusable, modular, and testable function `check_permission_for_asset_id` within `libs/sharing`.
- This function should accept user context (`AuthenticatedUser` containing cached org roles), asset details (ID, type, org ID), and required permission levels.
- It should first check the user's cached organization roles (`user.organizations`) for high-level admin privileges (`WorkspaceAdmin`, `DataAdmin`).
- If no admin role applies, it should query the `asset_permissions` table for direct permissions for the specific asset ID and user.
- It should return `Ok(true)` if the user meets the requirements (either via org admin role or direct permission), `Ok(false)` if they don't, and `Err` only for database query errors.
2025-04-08 23:20:20 +08:00
## 4. Non-Goals
2025-04-09 00:29:39 +08:00
- Replacing the efficient fetch helpers like `fetch_metric_file_with_permissions` used by metadata retrieval handlers.
- Querying the `users_to_organizations` table (relies on cached roles in `AuthenticatedUser`).
- Handling complex permission inheritance beyond direct permissions and high-level org roles (unless explicitly added).
2025-04-08 23:20:20 +08:00
## 5. Technical Design
2025-04-09 00:29:39 +08:00
1. **Location:** `libs/sharing/src/permissions.rs` (or similar).
2. **Function Signature & Implementation:**
2025-04-08 23:20:20 +08:00
```rust
// libs/sharing/src/permissions.rs
2025-04-09 00:29:39 +08:00
use anyhow::{anyhow, Result};
use diesel::prelude::*;
use diesel::dsl::exists;
2025-04-08 23:20:20 +08:00
use diesel_async::AsyncPgConnection;
use middleware::AuthenticatedUser;
2025-04-09 00:29:39 +08:00
use uuid::Uuid;
use database::enums::{AssetPermissionRole, AssetType, UserOrganizationRole, UserOrganizationStatus};
use database::schema::asset_permissions;
/// Checks if a user has the required permission level for a specific asset ID,
/// leveraging cached organization roles and querying direct permissions.
/// Intended primarily for pre-execution checks.
pub async fn check_permission_for_asset_id(
2025-04-08 23:20:20 +08:00
conn: &mut AsyncPgConnection,
user: &AuthenticatedUser,
asset_id: &Uuid,
asset_type: AssetType,
2025-04-09 00:29:39 +08:00
asset_organization_id: Uuid, // Org ID of the asset itself
2025-04-08 23:20:20 +08:00
required_roles: &[AssetPermissionRole],
) -> Result<bool> {
2025-04-09 00:29:39 +08:00
// --- 1. Check Cached High-Level Organization Roles First ---
let high_level_org_roles = [
2025-04-09 00:29:39 +08:00
UserOrganizationRole::WorkspaceAdmin,
UserOrganizationRole::DataAdmin,
];
2025-04-09 00:29:39 +08:00
let has_high_level_org_role = user.organizations.iter().any(|org| {
org.id == asset_organization_id && high_level_org_roles.contains(&org.role)
// Assuming AuthenticatedUser.organizations only contains active memberships
});
2025-04-09 00:29:39 +08:00
if has_high_level_org_role {
return Ok(true); // User is Org Admin/Data Admin for the asset's org, grant access
}
2025-04-09 00:29:39 +08:00
// --- 2. Check Direct Permissions (Database Query) ---
2025-04-08 23:20:20 +08:00
let direct_permission_exists = select(exists(
asset_permissions::table
.filter(asset_permissions::asset_id.eq(asset_id))
.filter(asset_permissions::asset_type.eq(asset_type))
2025-04-09 00:29:39 +08:00
.filter(asset_permissions::identity_id.eq(user.id))
2025-04-08 23:20:20 +08:00
.filter(asset_permissions::identity_type.eq(database::enums::IdentityType::User))
2025-04-09 00:29:39 +08:00
.filter(asset_permissions::deleted_at.is_null()) // Ignore deleted permissions
2025-04-08 23:20:20 +08:00
.filter(asset_permissions::role.eq_any(required_roles)),
))
.get_result::<bool>(conn)
.await;
match direct_permission_exists {
2025-04-09 00:29:39 +08:00
Ok(true) => Ok(true), // Found sufficient direct permission
Ok(false) => Ok(false), // No sufficient direct permission found
2025-04-08 23:20:20 +08:00
Err(e) => {
tracing::error!(
2025-04-09 00:29:39 +08:00
"DB error checking direct asset permissions for user {} asset {} type {:?}: {}",
user.id, asset_id, asset_type, e
2025-04-08 23:20:20 +08:00
);
2025-04-09 00:29:39 +08:00
Err(anyhow!("Failed to check direct asset permissions: {}", e))
2025-04-08 23:20:20 +08:00
}
}
2025-04-09 00:29:39 +08:00
// Note: No check for Member role here unless specifically required
// for action execution contexts, as metadata handlers use different logic.
2025-04-08 23:20:20 +08:00
}
```
3. **File Changes:**
- Create/Modify: `libs/sharing/src/permissions.rs`
2025-04-09 00:29:39 +08:00
- Modify: `libs/sharing/src/lib.rs` (to export the function)
2025-04-08 23:20:20 +08:00
## 6. Implementation Plan
2025-04-09 00:29:39 +08:00
1. Implement the `check_permission_for_asset_id` function as defined above.
2. Add comprehensive unit tests.
3. Export the function.
2025-04-08 23:20:20 +08:00
## 7. Testing Strategy
- **Unit Tests:**
2025-04-09 00:29:39 +08:00
- Mock `AuthenticatedUser` with various `organizations` states.
- Mock `AsyncPgConnection` and `asset_permissions` query results.
2025-04-08 23:20:20 +08:00
- Test cases:
2025-04-09 00:29:39 +08:00
- User has `WorkspaceAdmin` role in cache for the asset's org -> returns `Ok(true)` without DB query.
- User has `DataAdmin` role in cache -> returns `Ok(true)` without DB query.
- User has other role in cache, direct `CanView` in DB -> returns `Ok(true)` when `CanView` required.
- User has other role in cache, direct `CanView` in DB -> returns `Ok(false)` when `Owner` required.
- User has other role in cache, no direct permission in DB -> returns `Ok(false)`.
- User has no role in cache for asset's org, direct `CanView` in DB -> returns `Ok(true)` when `CanView` required.
- User has no role in cache, no direct permission -> returns `Ok(false)`.
- Direct permission exists but `deleted_at` is set -> returns `Ok(false)`.
2025-04-08 23:20:20 +08:00
- Database error during direct permission check -> returns `Err`.
## 8. Rollback Plan
2025-04-09 00:29:39 +08:00
- Revert the changes to `libs/sharing`.
2025-04-08 23:20:20 +08:00
## 9. Dependencies
2025-04-09 00:29:39 +08:00
- None.