feat(authz): check permission on read handlers + check create permission on folder

This commit is contained in:
Edouard Vanbelle
2026-05-21 11:07:04 +02:00
parent cba9be8c21
commit 3362e277ab
21 changed files with 428 additions and 180 deletions
@@ -36,12 +36,25 @@ pub trait AuthorizationEngine: Send + Sync + 'static {
resource: Resource,
) -> Result<(), DomainError> {
if self.check(subject, permission, resource).await? {
tracing::debug!(
"👮🏻‍♂️ perms: ✔ Subject '{}' has permission to '{}' on resource '{}'",
subject,
permission,
resource
);
Ok(())
} else {
let (kind, id) = match resource {
Resource::Folder(id) => ("Folder", id),
Resource::File(id) => ("File", id),
};
// log it for audit
tracing::info!(
"👮🏻‍♂️ perms: ⛔ Subject '{}' hasn't permission to '{}' on resource '{}'",
subject,
permission,
resource
);
Err(DomainError::not_found(kind, id.to_string()))
}
}
+14 -6
View File
@@ -11,6 +11,7 @@ use crate::application::services::file_management_service::FileManagementService
use crate::application::services::file_retrieval_service::FileRetrievalService;
use crate::application::services::file_upload_service::FileUploadService;
use crate::common::errors::DomainError;
use crate::domain::services::authorization::Permission;
// ─────────────────────────────────────────────────────
// Upload port
@@ -119,7 +120,7 @@ pub trait FileRetrievalUseCase: Send + Sync + 'static {
///
/// Returns `NotFound` if the file does not exist **or** belongs to
/// another user. All user-facing handlers should use this method.
async fn get_file_owned(&self, id: &str, caller_id: Uuid) -> Result<FileDto, DomainError>;
async fn get_file_with_perms(&self, id: &str, caller_id: Uuid) -> Result<FileDto, DomainError>;
/// Gets a file by its path (for WebDAV)
async fn get_file_by_path(&self, path: &str) -> Result<FileDto, DomainError>;
@@ -131,7 +132,7 @@ pub trait FileRetrievalUseCase: Send + Sync + 'static {
///
/// Uses SQL-level `AND user_id` filtering — no in-memory post-filter.
/// All user-facing list handlers should use this method.
async fn list_files_owned(
async fn list_files_with_perms(
&self,
folder_id: Option<&str>,
owner_id: Uuid,
@@ -144,7 +145,7 @@ pub trait FileRetrievalUseCase: Send + Sync + 'static {
) -> Result<Box<dyn Stream<Item = Result<Bytes, std::io::Error>> + Send>, DomainError>;
/// Gets file content as a stream, enforcing that `caller_id` is the owner.
async fn get_file_stream_owned(
async fn get_file_stream_with_perms(
&self,
id: &str,
caller_id: Uuid,
@@ -166,7 +167,7 @@ pub trait FileRetrievalUseCase: Send + Sync + 'static {
///
/// Verifies `caller_id` owns the file before returning content.
/// All user-facing download handlers should use this.
async fn get_file_optimized_owned(
async fn get_file_optimized_with_perms(
&self,
id: &str,
caller_id: Uuid,
@@ -198,7 +199,7 @@ pub trait FileRetrievalUseCase: Send + Sync + 'static {
) -> Result<Box<dyn Stream<Item = Result<Bytes, std::io::Error>> + Send>, DomainError>;
/// Ownership-scoped range stream — verifies caller owns the file first.
async fn get_file_range_stream_owned(
async fn get_file_range_stream_with_perms(
&self,
id: &str,
caller_id: Uuid,
@@ -238,7 +239,7 @@ pub trait FileRetrievalUseCase: Send + Sync + 'static {
///
/// Used by streaming WebDAV PROPFIND so that each user only sees their
/// own files, even in shared folder_id namespaces.
async fn list_files_batch_for_owner(
async fn list_files_batch_with_perms(
&self,
folder_id: Option<&str>,
owner_id: Uuid,
@@ -256,6 +257,13 @@ pub trait FileRetrievalUseCase: Send + Sync + 'static {
/// Primary port for file management operations
pub trait FileManagementUseCase: Send + Sync + 'static {
async fn has_permission(
&self,
caller_id: Uuid,
permission: Permission,
file_id: &str,
) -> Result<(), DomainError>;
/// Moves a file, enforcing that `caller_id` is the owner.
async fn move_file_with_perms(
&self,
+10 -2
View File
@@ -6,8 +6,16 @@ use crate::application::dtos::folder_dto::{
};
use crate::common::errors::DomainError;
use crate::domain::services::authorization::Permission;
pub trait FolderUseCase: Send + Sync + 'static {
async fn has_permission(
&self,
caller_id: Uuid,
permission: Permission,
folder_id: &str,
) -> Result<(), DomainError>;
/// Creates a new folder
async fn create_folder_with_perms(
&self,
@@ -36,7 +44,7 @@ pub trait FolderUseCase: Send + Sync + 'static {
/// Lists folders scoped to a specific owner (for user-facing endpoints).
/// At root level, only returns folders belonging to this user.
async fn list_folders_for_owner(
async fn list_folders_with_perms(
&self,
parent_id: Option<&str>,
owner_id: Uuid,
@@ -50,7 +58,7 @@ pub trait FolderUseCase: Send + Sync + 'static {
) -> Result<crate::application::dtos::pagination::PaginatedResponseDto<FolderDto>, DomainError>;
/// Lists folders with pagination, scoped to a specific owner.
async fn list_folders_for_owner_paginated(
async fn list_folders_paginated_with_perms(
&self,
parent_id: Option<&str>,
owner_id: Uuid,