diff --git a/CHANGELOG.md b/CHANGELOG.md index 2be5c975..1b7f874d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - [631](https://github.com/thoth-pub/thoth/issues/631) - Fix slow loading of Contributor dropdown in Contribution form +### Changed +- [650](https://github.com/thoth-pub/thoth/issues/650) - Allow only superusers to create/update/delete a `Location` when the `LocationPlatform` is `THOTH`. + ## [[0.12.14]](https://github.com/thoth-pub/thoth/releases/tag/v0.12.14) - 2024-11-04 ### Changed - [642](https://github.com/thoth-pub/thoth/issues/642) - Output `ProductAvailability` based on work status in Thoth ONIX 3.0 diff --git a/thoth-api/src/graphql/model.rs b/thoth-api/src/graphql/model.rs index cec8b994..339ff4e1 100644 --- a/thoth-api/src/graphql/model.rs +++ b/thoth-api/src/graphql/model.rs @@ -1615,6 +1615,11 @@ impl MutationRoot { #[graphql(description = "Values for location to be created")] data: NewLocation, ) -> FieldResult { context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + // Only superusers can create new locations where Location Platform is Thoth + if !context.account_access.is_superuser && data.location_platform == LocationPlatform::Thoth + { + return Err(ThothError::ThothLocationError.into()); + } context .account_access .can_edit(publisher_id_from_publication_id( @@ -1970,12 +1975,33 @@ impl MutationRoot { #[graphql(description = "Values to apply to existing location")] data: PatchLocation, ) -> FieldResult { context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; - let location = Location::from_id(&context.db, &data.location_id).unwrap(); + let current_location = Location::from_id(&context.db, &data.location_id).unwrap(); + let has_canonical_thoth_location = Publication::from_id(&context.db, &data.publication_id)? + .locations( + context, + Some(1), + None, + None, + Some(vec![LocationPlatform::Thoth]), + )? + .first() + .map_or(false, |location| location.canonical); + // Only superusers can update the canonical location when a Thoth Location Platform canonical location already exists + if has_canonical_thoth_location && data.canonical && !context.account_access.is_superuser { + return Err(ThothError::ThothUpdateCanonicalError.into()); + } + + // Only superusers can edit locations where Location Platform is Thoth + if !context.account_access.is_superuser + && current_location.location_platform == LocationPlatform::Thoth + { + return Err(ThothError::ThothLocationError.into()); + } context .account_access - .can_edit(location.publisher_id(&context.db)?)?; + .can_edit(current_location.publisher_id(&context.db)?)?; - if data.publication_id != location.publication_id { + if data.publication_id != current_location.publication_id { context .account_access .can_edit(publisher_id_from_publication_id( @@ -1989,7 +2015,7 @@ impl MutationRoot { } let account_id = context.token.jwt.as_ref().unwrap().account_id(&context.db); - location + current_location .update(&context.db, &data, &account_id) .map_err(|e| e.into()) } @@ -2294,6 +2320,12 @@ impl MutationRoot { ) -> FieldResult { context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; let location = Location::from_id(&context.db, &location_id).unwrap(); + // Only superusers can delete locations where Location Platform is Thoth + if !context.account_access.is_superuser + && location.location_platform == LocationPlatform::Thoth + { + return Err(ThothError::ThothLocationError.into()); + } context .account_access .can_edit(location.publisher_id(&context.db)?)?; diff --git a/thoth-app/src/component/locations_form.rs b/thoth-app/src/component/locations_form.rs index 375a3219..beb4eff5 100644 --- a/thoth-app/src/component/locations_form.rs +++ b/thoth-app/src/component/locations_form.rs @@ -1,4 +1,5 @@ use std::str::FromStr; +use thoth_api::account::model::AccountDetails; use thoth_api::model::location::Location; use thoth_api::model::location::LocationPlatform; use thoth_errors::ThothError; @@ -84,6 +85,7 @@ pub struct Props { pub locations: Option>, pub publication_id: Uuid, pub update_locations: Callback<()>, + pub current_user: AccountDetails, } impl Component for LocationsFormComponent { @@ -145,7 +147,20 @@ impl Component for LocationsFormComponent { { FetchState::NotFetching(_) => vec![], FetchState::Fetching(_) => vec![], - FetchState::Fetched(body) => body.data.location_platforms.enum_values.clone(), + FetchState::Fetched(body) => { + if ctx.props().current_user.resource_access.is_superuser { + body.data.location_platforms.enum_values.clone() + // remove Thoth from LocationPlatform enum for non-superusers + } else { + body.data + .location_platforms + .enum_values + .clone() + .into_iter() + .filter(|platform| platform.name != LocationPlatform::Thoth) + .collect() + } + } FetchState::Failed(_, _err) => vec![], }; true @@ -462,13 +477,28 @@ impl LocationsFormComponent { ctx.link() .callback(move |_| Msg::DeleteLocation(location_id)), ); + let mut edit_callback = Some( + ctx.link() + .callback(move |_| Msg::ToggleModalFormDisplay(true, Some(location.clone()))), + ); let mut delete_deactivated = false; + let mut edit_deactivated = false; + // If the location is canonical and other (non-canonical) locations exist, prevent it from // being deleted by deactivating the delete button and unsetting its callback attribute if l.canonical && ctx.props().locations.as_ref().unwrap_or(&vec![]).len() > 1 { delete_callback = None; delete_deactivated = true; } + // If not superuser, restrict deleting and editing locations with Thoth location platform + if !ctx.props().current_user.resource_access.is_superuser + && l.location_platform == LocationPlatform::Thoth + { + delete_callback = None; + delete_deactivated = true; + edit_callback = None; + edit_deactivated = true; + } html! {
@@ -510,7 +540,8 @@ impl LocationsFormComponent {
{ EDIT_BUTTON } diff --git a/thoth-app/src/component/publication.rs b/thoth-app/src/component/publication.rs index d14ec9b2..dcb89837 100644 --- a/thoth-app/src/component/publication.rs +++ b/thoth-app/src/component/publication.rs @@ -416,6 +416,7 @@ impl Component for PublicationComponent {