diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 172c7f90f8f..f5b739d0dff 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -9,7 +9,9 @@ use crate::{ ApiResult, EmptyResult, JsonResult, JsonUpcase, JsonUpcaseVec, JsonVec, Notify, NumberOrString, PasswordData, UpdateType, }, - auth::{decode_invite, AdminHeaders, Headers, ManagerHeaders, ManagerHeadersLoose, OwnerHeaders}, + auth::{ + decode_invite, AdminHeaders, Headers, ManagerHeaders, ManagerHeadersBulk, ManagerHeadersLoose, OwnerHeaders, + }, db::{models::*, DbConn}, error::Error, mail, @@ -39,6 +41,7 @@ pub fn routes() -> Vec { put_organization_collection_update, delete_organization_collection, post_organization_collection_delete, + bulk_delete_organization_collections, get_org_details, get_org_users, send_invite, @@ -538,14 +541,13 @@ async fn post_organization_collection_delete_user( delete_organization_collection_user(org_id, col_id, org_user_id, headers, conn).await } -#[delete("/organizations//collections/")] -async fn delete_organization_collection( +async fn _delete_organization_collection( org_id: String, col_id: String, - headers: ManagerHeaders, - mut conn: DbConn, + headers: &ManagerHeaders, + conn: &mut DbConn, ) -> EmptyResult { - match Collection::find_by_uuid(&col_id, &mut conn).await { + match Collection::find_by_uuid(&col_id, conn).await { None => err!("Collection not found"), Some(collection) => { if collection.org_uuid == org_id { @@ -556,10 +558,10 @@ async fn delete_organization_collection( headers.user.uuid.clone(), headers.device.atype, &headers.ip.ip, - &mut conn, + conn, ) .await; - collection.delete(&mut conn).await + collection.delete(conn).await } else { err!("Collection and Organization id do not match") } @@ -567,6 +569,16 @@ async fn delete_organization_collection( } } +#[delete("/organizations//collections/")] +async fn delete_organization_collection( + org_id: String, + col_id: String, + headers: ManagerHeaders, + mut conn: DbConn, +) -> EmptyResult { + _delete_organization_collection(org_id, col_id, &headers, &mut conn).await +} + #[derive(Deserialize, Debug)] #[allow(non_snake_case, dead_code)] struct DeleteCollectionData { @@ -580,9 +592,34 @@ async fn post_organization_collection_delete( col_id: String, headers: ManagerHeaders, _data: JsonUpcase, - conn: DbConn, + mut conn: DbConn, +) -> EmptyResult { + _delete_organization_collection(org_id, col_id, &headers, &mut conn).await +} + +#[derive(Deserialize, Debug)] +#[allow(non_snake_case)] +struct BulkCollectionIds { + Ids: Vec, + OrganizationId: String, +} + +#[delete("/organizations//collections", data = "")] +async fn bulk_delete_organization_collections( + org_id: String, + headers: ManagerHeadersBulk, + data: JsonUpcase, + mut conn: DbConn, ) -> EmptyResult { - delete_organization_collection(org_id, col_id, headers, conn).await + let data: BulkCollectionIds = data.into_inner().data; + assert!(org_id == data.OrganizationId); + + let headers = ManagerHeaders::from(headers); + + for col_id in data.Ids { + _delete_organization_collection(org_id.clone(), col_id, &headers, &mut conn).await? + } + Ok(()) } #[get("/organizations//collections//details")] diff --git a/src/auth.rs b/src/auth.rs index e5779dd6ce2..9a1144e4d55 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -573,6 +573,10 @@ fn get_col_id(request: &Request<'_>) -> Option { None } +async fn can_access_collection(col_id: String, user: &UserOrganization, conn: &mut DbConn) -> bool { + user.has_full_access() || Collection::has_access_by_collection_and_user_uuid(&col_id, &user.user_uuid, conn).await +} + /// The ManagerHeaders are used to check if you are at least a Manager /// and have access to the specific collection provided via the /collections/collectionId. /// This does strict checking on the collection_id, ManagerHeadersLoose does not. @@ -590,6 +594,7 @@ impl<'r> FromRequest<'r> for ManagerHeaders { async fn from_request(request: &'r Request<'_>) -> Outcome { let headers = try_outcome!(OrgHeaders::from_request(request).await); + if headers.org_user_type >= UserOrgType::Manager { match get_col_id(request) { Some(col_id) => { @@ -597,15 +602,7 @@ impl<'r> FromRequest<'r> for ManagerHeaders { Outcome::Success(conn) => conn, _ => err_handler!("Error getting DB"), }; - - if !headers.org_user.has_full_access() - && !Collection::has_access_by_collection_and_user_uuid( - &col_id, - &headers.org_user.user_uuid, - &mut conn, - ) - .await - { + if !can_access_collection(col_id, &headers.org_user, &mut conn).await { err_handler!("The current user isn't a manager for this collection") } } @@ -677,6 +674,51 @@ impl From for Headers { } } +// ManagerHeadersBulk is used to bulk delete the given collections and can be converted to +// ManagerHeaders +pub struct ManagerHeadersBulk { + pub host: String, + pub device: Device, + pub user: User, + pub org_user_type: UserOrgType, + pub ip: ClientIp, +} + +#[rocket::async_trait] +impl<'r> FromRequest<'r> for ManagerHeadersBulk { + type Error = &'static str; + + async fn from_request(request: &'r Request<'_>) -> Outcome { + let headers = try_outcome!(OrgHeaders::from_request(request).await); + + if headers.org_user_type < UserOrgType::Manager { + err_handler!("You need to be a Manager, Admin or Owner to call this endpoint") + } + + // TODO: implement guard to check if user has access to the given collections + + Outcome::Success(Self { + host: headers.host, + device: headers.device, + user: headers.user, + org_user_type: headers.org_user_type, + ip: headers.ip, + }) + } +} + +impl From for ManagerHeaders { + fn from(h: ManagerHeadersBulk) -> ManagerHeaders { + ManagerHeaders { + host: h.host, + device: h.device, + user: h.user, + org_user_type: h.org_user_type, + ip: h.ip, + } + } +} + pub struct OwnerHeaders { pub host: String, pub device: Device,