refactor(create_folder): add an ownership check while creating a folder + refactor code
This commit is contained in:
@@ -13,7 +13,11 @@ use crate::common::errors::DomainError;
|
||||
/// Primary port for folder operations
|
||||
pub trait FolderUseCase: Send + Sync + 'static {
|
||||
/// Creates a new folder
|
||||
async fn create_folder(&self, dto: CreateFolderDto) -> Result<FolderDto, DomainError>;
|
||||
async fn create_folder(
|
||||
&self,
|
||||
dto: CreateFolderDto,
|
||||
caller_id: Uuid,
|
||||
) -> Result<FolderDto, DomainError>;
|
||||
|
||||
/// Gets a folder by its ID
|
||||
async fn get_folder(&self, id: &str) -> Result<FolderDto, DomainError>;
|
||||
|
||||
@@ -975,18 +975,11 @@ impl BatchOperationService {
|
||||
let folder_service = self.folder_service.clone();
|
||||
|
||||
async move {
|
||||
// If a parent is specified, verify the caller owns it
|
||||
if let Some(ref pid) = parent_id
|
||||
&& let Err(e) = folder_service.get_folder_owned(pid, user_id).await
|
||||
{
|
||||
let id = format!("{}:{}", name, pid);
|
||||
return (id, Err(e));
|
||||
}
|
||||
let dto = crate::application::dtos::folder_dto::CreateFolderDto {
|
||||
name: name.clone(),
|
||||
parent_id: parent_id.clone(),
|
||||
};
|
||||
let create_result = folder_service.create_folder(dto).await;
|
||||
let create_result = folder_service.create_folder(dto, user_id).await;
|
||||
let id = format!("{}:{}", name, parent_id.unwrap_or_default());
|
||||
(id, create_result)
|
||||
}
|
||||
|
||||
@@ -82,33 +82,26 @@ impl FileManagementService {
|
||||
}
|
||||
|
||||
/// Verifies that the target folder is owned by the caller.
|
||||
/// If folder_id is None (root), ownership is implicitly granted.
|
||||
///
|
||||
/// `None` means the target is the user's root namespace
|
||||
/// (`storage.files.folder_id IS NULL`) — implicitly owned by the caller, so
|
||||
/// the check is skipped. Fails closed if `folder_repo` was not injected.
|
||||
async fn verify_target_folder_owner(
|
||||
&self,
|
||||
folder_id: &Option<String>,
|
||||
folder_id: Option<&str>,
|
||||
caller_id: Uuid,
|
||||
) -> Result<(), DomainError> {
|
||||
let folder_id = match folder_id {
|
||||
Some(id) => id,
|
||||
None => return Ok(()), // Moving to root is always allowed
|
||||
let Some(target) = folder_id else {
|
||||
// TODO: File creation to root is currently allowed, check is this policy is relevant
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if let Some(folder_repo) = &self.folder_repo {
|
||||
let folder_owner = folder_repo.get_folder_user_id(folder_id).await?;
|
||||
if folder_owner != caller_id {
|
||||
return Err(DomainError::not_found(
|
||||
"Folder",
|
||||
"Target folder not found or access denied",
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
// Fallback: no folder repo injected — deny by default (fail-closed)
|
||||
Err(DomainError::internal_error(
|
||||
let Some(folder_repo) = &self.folder_repo else {
|
||||
return Err(DomainError::internal_error(
|
||||
"FileManagement",
|
||||
"Folder ownership verification unavailable",
|
||||
))
|
||||
}
|
||||
));
|
||||
};
|
||||
folder_repo.verify_owner(target, caller_id).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +144,7 @@ impl FileManagementUseCase for FileManagementService {
|
||||
// Verify file ownership first
|
||||
self.verify_owner(file_id, caller_id).await?;
|
||||
// Verify target folder ownership (prevents file from "disappearing")
|
||||
self.verify_target_folder_owner(&folder_id, caller_id)
|
||||
self.verify_target_folder_owner(folder_id.as_deref(), caller_id)
|
||||
.await?;
|
||||
self.move_file(file_id, folder_id).await
|
||||
}
|
||||
@@ -192,7 +185,7 @@ impl FileManagementUseCase for FileManagementService {
|
||||
target_folder_id: Option<String>,
|
||||
) -> Result<FileDto, DomainError> {
|
||||
self.verify_owner(file_id, caller_id).await?;
|
||||
self.verify_target_folder_owner(&target_folder_id, caller_id)
|
||||
self.verify_target_folder_owner(target_folder_id.as_deref(), caller_id)
|
||||
.await?;
|
||||
self.copy_file(file_id, target_folder_id).await
|
||||
}
|
||||
@@ -334,21 +327,11 @@ impl FileManagementUseCase for FileManagementService {
|
||||
target_parent_id: Option<String>,
|
||||
dest_name: Option<String>,
|
||||
) -> Result<CopyFolderTreeResult, DomainError> {
|
||||
if let Some(folder_repo) = &self.folder_repo {
|
||||
let owner = folder_repo.get_folder_user_id(source_folder_id).await?;
|
||||
if owner != caller_id {
|
||||
return Err(DomainError::not_found(
|
||||
"Folder",
|
||||
"Source folder not found or access denied",
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(DomainError::internal_error(
|
||||
"FileManagement",
|
||||
"Folder ownership verification unavailable",
|
||||
));
|
||||
}
|
||||
self.verify_target_folder_owner(&target_parent_id, caller_id)
|
||||
// Source ownership: source_folder_id is required (not optional), but reuse the
|
||||
// wrapper which also enforces the fail-closed semantics if folder_repo is absent.
|
||||
self.verify_target_folder_owner(Some(source_folder_id), caller_id)
|
||||
.await?;
|
||||
self.verify_target_folder_owner(target_parent_id.as_deref(), caller_id)
|
||||
.await?;
|
||||
self.copy_folder_tree(source_folder_id, target_parent_id, dest_name)
|
||||
.await
|
||||
|
||||
@@ -25,7 +25,11 @@ impl FolderService {
|
||||
struct FolderServiceStub;
|
||||
|
||||
impl FolderUseCase for FolderServiceStub {
|
||||
async fn create_folder(&self, _dto: CreateFolderDto) -> Result<FolderDto, DomainError> {
|
||||
async fn create_folder(
|
||||
&self,
|
||||
_dto: CreateFolderDto,
|
||||
_user_id: Uuid,
|
||||
) -> Result<FolderDto, DomainError> {
|
||||
Ok(FolderDto::empty())
|
||||
}
|
||||
|
||||
@@ -134,8 +138,11 @@ impl FolderService {
|
||||
|
||||
impl FolderUseCase for FolderService {
|
||||
/// Creates a new folder
|
||||
async fn create_folder(&self, dto: CreateFolderDto) -> Result<FolderDto, DomainError> {
|
||||
// Input validation
|
||||
async fn create_folder(
|
||||
&self,
|
||||
dto: CreateFolderDto,
|
||||
caller_id: Uuid,
|
||||
) -> Result<FolderDto, DomainError> {
|
||||
if let Err(reason) = validate_storage_name(&dto.name) {
|
||||
return Err(DomainError::validation_error(format!(
|
||||
"Invalid folder name '{}': {reason}",
|
||||
@@ -143,21 +150,19 @@ impl FolderUseCase for FolderService {
|
||||
)));
|
||||
}
|
||||
|
||||
// If a parent_id is provided, verify it exists
|
||||
if let Some(parent_id) = &dto.parent_id {
|
||||
let parent_exists = self.folder_storage.get_folder(parent_id).await.is_ok();
|
||||
if !parent_exists {
|
||||
return Err(DomainError::not_found("Folder", parent_id));
|
||||
}
|
||||
}
|
||||
let Some(parent_id) = dto.parent_id.as_deref() else {
|
||||
return Err(DomainError::validation_error(
|
||||
"Root folder creation is reserved for registration",
|
||||
));
|
||||
};
|
||||
self.folder_storage
|
||||
.verify_owner(parent_id, caller_id)
|
||||
.await?;
|
||||
|
||||
// Create the folder
|
||||
let folder = self
|
||||
.folder_storage
|
||||
.create_folder(dto.name, dto.parent_id)
|
||||
.await?;
|
||||
|
||||
// Convert to DTO
|
||||
Ok(FolderDto::from(folder))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user