use std::sync::Arc;
use cognee_database::{AclDb, DeleteDb};
use uuid::Uuid;
use crate::{DeleteError, DeletePreview, DeleteRequest, DeleteResult, DeleteScope, DeleteService};
pub struct AuthorizedDeleteService {
inner: DeleteService,
acl_db: Arc<dyn AclDb>,
database: Arc<dyn DeleteDb>,
}
impl AuthorizedDeleteService {
pub fn new(inner: DeleteService, acl_db: Arc<dyn AclDb>, database: Arc<dyn DeleteDb>) -> Self {
Self {
inner,
acl_db,
database,
}
}
pub async fn preview(
&self,
request: &DeleteRequest,
principal_id: Uuid,
) -> Result<DeletePreview, DeleteError> {
self.check_authorization(request, principal_id).await?;
self.inner.preview(request).await
}
pub async fn execute(
&self,
request: &DeleteRequest,
principal_id: Uuid,
) -> Result<DeleteResult, DeleteError> {
self.check_authorization(request, principal_id).await?;
self.inner.execute(request).await
}
async fn check_authorization(
&self,
request: &DeleteRequest,
principal_id: Uuid,
) -> Result<(), DeleteError> {
match &request.scope {
DeleteScope::Data {
owner_id,
data_id,
dataset_name,
..
} => {
if let Some(ds_name) = dataset_name {
let dataset_id = self.resolve_dataset_id(*owner_id, ds_name).await?;
self.require_delete_permission(principal_id, dataset_id)
.await?;
} else {
let datasets = self
.database
.list_datasets_for_data(*data_id)
.await
.map_err(|e| {
DeleteError::Runtime(format!(
"Failed to list datasets for data {data_id}: {e}"
))
})?;
for ds in &datasets {
if ds.owner_id == *owner_id {
self.require_delete_permission(principal_id, ds.id).await?;
}
}
}
}
DeleteScope::Dataset {
owner_id,
dataset_name,
} => {
let dataset_id = self.resolve_dataset_id(*owner_id, dataset_name).await?;
self.require_delete_permission(principal_id, dataset_id)
.await?;
}
DeleteScope::User { owner_id } => {
let authorized = self
.acl_db
.authorized_dataset_ids(principal_id, "delete")
.await
.map_err(|e| DeleteError::Runtime(format!("ACL query failed: {e}")))?;
let owner_datasets = self
.database
.list_datasets_by_owner(*owner_id)
.await
.map_err(|e| {
DeleteError::Runtime(format!("Failed to list owner datasets: {e}"))
})?;
for ds in &owner_datasets {
if !authorized.contains(&ds.id) {
return Err(DeleteError::PermissionDenied(format!(
"Principal {} does not have 'delete' permission on dataset '{}'",
principal_id, ds.name
)));
}
}
}
DeleteScope::All => {
let authorized = self
.acl_db
.authorized_dataset_ids(principal_id, "delete")
.await
.map_err(|e| DeleteError::Runtime(format!("ACL query failed: {e}")))?;
let all_datasets = self.database.list_datasets().await.map_err(|e| {
DeleteError::Runtime(format!("Failed to list all datasets: {e}"))
})?;
for ds in &all_datasets {
if !authorized.contains(&ds.id) {
return Err(DeleteError::PermissionDenied(format!(
"Principal {} does not have 'delete' permission on dataset '{}'",
principal_id, ds.name
)));
}
}
}
}
Ok(())
}
async fn resolve_dataset_id(
&self,
owner_id: Uuid,
dataset_name: &str,
) -> Result<Uuid, DeleteError> {
let dataset = self
.database
.get_dataset_by_name(dataset_name, owner_id, None)
.await
.map_err(|e| {
DeleteError::Runtime(format!("Failed to resolve dataset '{dataset_name}': {e}"))
})?
.ok_or_else(|| {
DeleteError::Validation(format!(
"Dataset '{dataset_name}' was not found for owner {owner_id}"
))
})?;
Ok(dataset.id)
}
async fn require_delete_permission(
&self,
principal_id: Uuid,
dataset_id: Uuid,
) -> Result<(), DeleteError> {
let has_perm = self
.acl_db
.has_permission(principal_id, dataset_id, "delete")
.await
.map_err(|e| DeleteError::Runtime(format!("ACL check failed: {e}")))?;
if !has_perm {
return Err(DeleteError::PermissionDenied(format!(
"Principal {principal_id} does not have 'delete' permission on dataset {dataset_id}"
)));
}
Ok(())
}
}