From 2f533072a313a9580e8130edf6a554dd73943197 Mon Sep 17 00:00:00 2001 From: Martin Hradil Date: Fri, 21 Jul 2023 04:04:44 +0000 Subject: [PATCH 01/17] Collection upload/deprecate - fix permission checks Issue: AAH-2439 Issue: AAH-2853 --- CHANGES/2439.bug | 1 + CHANGES/2853.bug | 1 + src/components/headers/collection-header.tsx | 60 +++++++++---------- .../namespace-detail/namespace-detail.tsx | 44 +++++++------- src/containers/search/search.tsx | 30 +++++----- 5 files changed, 69 insertions(+), 67 deletions(-) create mode 100644 CHANGES/2439.bug create mode 100644 CHANGES/2853.bug diff --git a/CHANGES/2439.bug b/CHANGES/2439.bug new file mode 100644 index 0000000000..29de98b9f3 --- /dev/null +++ b/CHANGES/2439.bug @@ -0,0 +1 @@ +Collection upload/deprecate - fix permission checks diff --git a/CHANGES/2853.bug b/CHANGES/2853.bug new file mode 100644 index 0000000000..29de98b9f3 --- /dev/null +++ b/CHANGES/2853.bug @@ -0,0 +1 @@ +Collection upload/deprecate - fix permission checks diff --git a/src/components/headers/collection-header.tsx b/src/components/headers/collection-header.tsx index 3687e08678..a1e54e526e 100644 --- a/src/components/headers/collection-header.tsx +++ b/src/components/headers/collection-header.tsx @@ -238,36 +238,34 @@ export class CollectionHeader extends React.Component { return ; } - const canSign = canSignNamespace(this.context, this.state.namespace); const { hasPermission } = this.context; const hasObjectPermission = (permission, namespace) => namespace?.related_fields?.my_permissions?.includes?.(permission); - const canDeleteCommunityCollection = - IS_COMMUNITY && - hasObjectPermission('galaxy.change_namespace', this.state.namespace); + const canDeleteCollection = + hasPermission('ansible.delete_collection') || + (IS_COMMUNITY && + hasObjectPermission('galaxy.change_namespace', this.state.namespace)); + const canSign = canSignNamespace(this.context, this.state.namespace); + const canUpload = hasPermission('galaxy.upload_to_namespace'); + const canDeprecate = canUpload; const dropdownItems = [ DeleteCollectionUtils.deleteMenuOption({ - canDeleteCollection: - hasPermission('ansible.delete_collection') || - canDeleteCommunityCollection, + canDeleteCollection, noDependencies, onClick: () => this.openDeleteModalWithConfirm(null, true), deleteAll: true, display_repositories: display_repositories, }), DeleteCollectionUtils.deleteMenuOption({ - canDeleteCollection: - hasPermission('ansible.delete_collection') || - canDeleteCommunityCollection, + canDeleteCollection, noDependencies, onClick: () => this.openDeleteModalWithConfirm(null, false), deleteAll: false, display_repositories: display_repositories, }), - (hasPermission('ansible.delete_collection') || - canDeleteCommunityCollection) && ( + canDeleteCollection && ( { {t`Delete version ${version} from system`} ), - (hasPermission('ansible.delete_collection') || - canDeleteCommunityCollection) && - display_repositories && ( - this.openDeleteModalWithConfirm(version, false)} - > - {t`Delete version ${version} from repository`} - - ), + canDeleteCollection && display_repositories && ( + this.openDeleteModalWithConfirm(version, false)} + > + {t`Delete version ${version} from repository`} + + ), canSign && !can_upload_signatures && ( { {t`Sign version ${version}`} ), - hasPermission('galaxy.upload_to_namespace') && ( + canDeprecate && ( this.deprecate(collection)} key='deprecate' @@ -322,13 +318,15 @@ export class CollectionHeader extends React.Component { {collection.is_deprecated ? t`Undeprecate` : t`Deprecate`} ), - this.checkUploadPrivilleges(collection)} - data-cy='upload-collection-version-dropdown' - > - {t`Upload new version`} - , + canUpload && ( + this.checkUploadPrivilleges(collection)} + data-cy='upload-collection-version-dropdown' + > + {t`Upload new version`} + + ), display_repositories && ( { namespace?.related_fields?.my_permissions?.includes?.(permission); const { showControls } = this.state; const { display_repositories } = this.context.featureFlags; - const canDeleteCommunityCollection = - IS_COMMUNITY && - hasObjectPermission('galaxy.change_namespace', this.state.namespace); + + const canDeleteCollection = + hasPermission('ansible.delete_collection') || + (IS_COMMUNITY && + hasObjectPermission('galaxy.change_namespace', this.state.namespace)); + const canUpload = hasPermission('galaxy.upload_to_namespace'); + const canDeprecate = canUpload; if (!showControls) { return; } return { - uploadButton: ( + uploadButton: canUpload && ( ), dropdownMenu: ( - - DeleteCollectionUtils.tryOpenDeleteModalWithConfirm({ - addAlert: (alert) => this.addAlert(alert), - setState: (state) => this.setState(state), - collection, - deleteAll: true, - }), + + DeleteCollectionUtils.tryOpenDeleteModalWithConfirm({ + addAlert: (alert) => this.addAlert(alert), + setState: (state) => this.setState(state), + collection, deleteAll: true, - display_repositories: display_repositories, - }), - DeleteCollectionUtils.deleteMenuOption({ - canDeleteCollection, - noDependencies: null, - onClick: () => - DeleteCollectionUtils.tryOpenDeleteModalWithConfirm({ - addAlert: (alert) => this.addAlert(alert), - setState: (state) => this.setState(state), - collection, - deleteAll: false, - }), + }) + } + onDeprecate={() => + this.handleCollectionAction( + collection.collection_version.pulp_href, + 'deprecate', + ) + } + onRemove={() => + DeleteCollectionUtils.tryOpenDeleteModalWithConfirm({ + addAlert: (alert) => this.addAlert(alert), + setState: (state) => this.setState(state), + collection, deleteAll: false, - display_repositories: display_repositories, - }), - canDeprecate && ( - - this.handleCollectionAction( - collection.collection_version.pulp_href, - 'deprecate', - ) - } - key='deprecate' - > - {collection.is_deprecated ? t`Undeprecate` : t`Deprecate`} - - ), - ].filter(Boolean)} - ariaLabel='collection-kebab' + }) + } /> ), }; diff --git a/src/containers/search/search.tsx b/src/containers/search/search.tsx index 0579228c57..212eec6d5a 100644 --- a/src/containers/search/search.tsx +++ b/src/containers/search/search.tsx @@ -1,5 +1,5 @@ import { t } from '@lingui/macro'; -import { Button, DataList, DropdownItem, Switch } from '@patternfly/react-core'; +import { Button, DataList, Switch } from '@patternfly/react-core'; import cx from 'classnames'; import React from 'react'; import { Navigate } from 'react-router-dom'; @@ -16,6 +16,7 @@ import { AlertType, BaseHeader, CollectionCard, + CollectionDropdown, CollectionListItem, CollectionNextPageCard, DeleteCollectionModal, @@ -361,66 +362,43 @@ class Search extends React.Component { private renderMenu(list, collection) { const { hasPermission } = this.context; - const hasObjectPermission = (permission, namespace) => - namespace?.related_fields?.my_permissions?.includes?.(permission); - const { display_repositories } = this.context.featureFlags; - - const canDeleteCollection = - hasPermission('ansible.delete_collection') || - (IS_COMMUNITY && - hasObjectPermission( - 'galaxy.change_namespace', - collection.collection_version.namespace, - )); const canUpload = hasPermission('galaxy.upload_to_namespace'); - const canDeprecate = canUpload; - const menuItems = [ - DeleteCollectionUtils.deleteMenuOption({ - canDeleteCollection, - noDependencies: null, - onClick: () => + const dropdownMenu = ( + DeleteCollectionUtils.tryOpenDeleteModalWithConfirm({ addAlert: (alert) => this.addAlert(alert), setState: (state) => this.setState(state), collection, deleteAll: true, - }), - deleteAll: true, - display_repositories: display_repositories, - }), - DeleteCollectionUtils.deleteMenuOption({ - canDeleteCollection, - noDependencies: null, - onClick: () => + }) + } + onDeprecate={() => this.handleControlClick(collection)} + onRemove={() => DeleteCollectionUtils.tryOpenDeleteModalWithConfirm({ addAlert: (alert) => this.addAlert(alert), setState: (state) => this.setState(state), collection, deleteAll: false, - }), - deleteAll: false, - display_repositories: display_repositories, - }), - canDeprecate && ( - this.handleControlClick(collection)} - key='deprecate' - > - {collection.is_deprecated ? t`Undeprecate` : t`Deprecate`} - - ), - !list && canUpload && ( - this.checkUploadPrivilleges(collection)} - key='upload new version' - > - {t`Upload new version`} - - ), - ].filter(Boolean); - - const displayMenu = menuItems.length > 0; + }) + } + onUploadVersion={ + list ? null : () => this.checkUploadPrivilleges(collection) + } + wrapper={ + list + ? null + : ({ any, children }) => + any ? ( + {children} + ) : ( + + ) + } + /> + ); if (list) { return { @@ -432,19 +410,11 @@ class Search extends React.Component { {t`Upload new version`} ) : null, - dropdownMenu: displayMenu ? ( - - ) : null, + dropdownMenu, }; } - return ( - - {displayMenu && ( - - )} - - ); + return dropdownMenu; } private renderSyncToogle(name: string, namespace: string): React.ReactNode { diff --git a/src/utilities/delete-collection.tsx b/src/utilities/delete-collection.tsx index 6fc4e3577c..77a40c51f9 100644 --- a/src/utilities/delete-collection.tsx +++ b/src/utilities/delete-collection.tsx @@ -6,7 +6,6 @@ import { CollectionVersionAPI, CollectionVersionSearch, } from 'src/api'; -import { Tooltip } from 'src/components'; import { errorMessage } from './fail-alerts'; import { parsePulpIDFromURL } from './parse-pulp-id'; import { repositoryRemoveCollection } from './repository-remove-collection'; @@ -27,53 +26,6 @@ export class DeleteCollectionUtils { }); } - public static deleteMenuOption({ - canDeleteCollection, - noDependencies, - onClick, - deleteAll, - display_repositories, - }) { - if (!canDeleteCollection) { - return null; - } - - if (!display_repositories && !deleteAll) { - // cant display delete from repository when repositories are turned off - return null; - } - - const caption = deleteAll - ? t`Delete entire collection from system` - : t`Delete collection from repository`; - - const key = deleteAll ? 'delete-collection' : 'remove-collection'; - - if (noDependencies === false) { - return ( - - Cannot delete until collections
- that depend on this collection
- have been deleted. - - } - > - {caption} -
- ); - } - - return ( - - {caption} - - ); - } - public static tryOpenDeleteModalWithConfirm({ addAlert, setState, From 03738b5452fd6f082b9358d253c5af3b90ebddb5 Mon Sep 17 00:00:00 2001 From: Martin Hradil Date: Tue, 21 Nov 2023 03:52:21 +0000 Subject: [PATCH 04/17] namespace detail respect namespace permissions --- src/components/collection-detail/collection-dropdown.tsx | 5 ++--- src/components/headers/collection-header.tsx | 1 - src/containers/namespace-detail/namespace-detail.tsx | 3 ++- src/containers/search/search.tsx | 2 -- src/utilities/delete-collection.tsx | 1 - 5 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/components/collection-detail/collection-dropdown.tsx b/src/components/collection-detail/collection-dropdown.tsx index 6b6843e8dc..dfc075e8d9 100644 --- a/src/components/collection-detail/collection-dropdown.tsx +++ b/src/components/collection-detail/collection-dropdown.tsx @@ -42,10 +42,9 @@ export const CollectionDropdown = ({ can_create_signatures, can_upload_signatures, display_repositories, - display_signatures, }, hasPermission, - user: { is_superuser }, + user: { is_anonymous, is_superuser }, } = useContext(); const hasObjectPermission = (permission) => @@ -56,7 +55,7 @@ export const CollectionDropdown = ({ hasObjectPermission(permission) || is_superuser; - const canCopy = display_repositories; + const canCopy = display_repositories && !is_anonymous; const canDelete = hasPerm('ansible.delete_collection') || hasPerm('galaxy.change_namespace'); const canDeprecate = hasPerm('galaxy.change_namespace'); diff --git a/src/components/headers/collection-header.tsx b/src/components/headers/collection-header.tsx index 839651dd20..c47abf63b3 100644 --- a/src/components/headers/collection-header.tsx +++ b/src/components/headers/collection-header.tsx @@ -46,7 +46,6 @@ import { RepoSelector, SignAllCertificatesModal, SignSingleCertificateModal, - StatefulDropdown, UploadSingCertificateModal, closeAlertMixin, } from 'src/components'; diff --git a/src/containers/namespace-detail/namespace-detail.tsx b/src/containers/namespace-detail/namespace-detail.tsx index 308bd3a935..77952736ae 100644 --- a/src/containers/namespace-detail/namespace-detail.tsx +++ b/src/containers/namespace-detail/namespace-detail.tsx @@ -1000,7 +1000,7 @@ export class NamespaceDetail extends React.Component { } private renderCollectionControls(collection: CollectionVersionSearch) { - const { showControls } = this.state; + const { namespace, showControls } = this.state; const { hasPermission } = this.context; const canUpload = hasPermission('galaxy.upload_to_namespace'); @@ -1026,6 +1026,7 @@ export class NamespaceDetail extends React.Component { dropdownMenu: ( DeleteCollectionUtils.tryOpenDeleteModalWithConfirm({ addAlert: (alert) => this.addAlert(alert), diff --git a/src/containers/search/search.tsx b/src/containers/search/search.tsx index 212eec6d5a..2b20ade0a8 100644 --- a/src/containers/search/search.tsx +++ b/src/containers/search/search.tsx @@ -1,6 +1,5 @@ import { t } from '@lingui/macro'; import { Button, DataList, Switch } from '@patternfly/react-core'; -import cx from 'classnames'; import React from 'react'; import { Navigate } from 'react-router-dom'; import { @@ -26,7 +25,6 @@ import { ImportModal, LoadingPageSpinner, Pagination, - StatefulDropdown, closeAlertMixin, collectionFilter, } from 'src/components'; diff --git a/src/utilities/delete-collection.tsx b/src/utilities/delete-collection.tsx index 77a40c51f9..31a7d42517 100644 --- a/src/utilities/delete-collection.tsx +++ b/src/utilities/delete-collection.tsx @@ -1,5 +1,4 @@ import { Trans, t } from '@lingui/macro'; -import { DropdownItem } from '@patternfly/react-core'; import React from 'react'; import { CollectionAPI, From b5227e0583b3d999215ee664f33f707830e28252 Mon Sep 17 00:00:00 2001 From: Martin Hradil Date: Wed, 29 Nov 2023 00:40:56 +0000 Subject: [PATCH 05/17] fix delete collectio/ns disabled message, use description --- .../collection-detail/collection-dropdown.tsx | 20 +++++++------------ .../namespace-detail/namespace-detail.tsx | 18 ++++------------- 2 files changed, 11 insertions(+), 27 deletions(-) diff --git a/src/components/collection-detail/collection-dropdown.tsx b/src/components/collection-detail/collection-dropdown.tsx index dfc075e8d9..741adaa9c6 100644 --- a/src/components/collection-detail/collection-dropdown.tsx +++ b/src/components/collection-detail/collection-dropdown.tsx @@ -1,7 +1,7 @@ -import { Trans, t } from '@lingui/macro'; +import { t } from '@lingui/macro'; import { DropdownItem } from '@patternfly/react-core'; import React from 'react'; -import { StatefulDropdown, Tooltip } from 'src/components'; +import { StatefulDropdown } from 'src/components'; import { useContext } from 'src/loaders/app-context'; interface IProps { @@ -80,18 +80,12 @@ export const CollectionDropdown = ({ onClick; }) => deletionBlocked ? ( - - Cannot delete until collections
- that depend on this collection
- have been deleted. - - } + - {caption} -
+ {caption} +
) : ( {caption} diff --git a/src/containers/namespace-detail/namespace-detail.tsx b/src/containers/namespace-detail/namespace-detail.tsx index 77952736ae..fc26b513e8 100644 --- a/src/containers/namespace-detail/namespace-detail.tsx +++ b/src/containers/namespace-detail/namespace-detail.tsx @@ -32,7 +32,6 @@ import { PartnerHeader, SignAllCertificatesModal, StatefulDropdown, - Tooltip, WisdomModal, closeAlertMixin, collectionFilter, @@ -847,19 +846,10 @@ export class NamespaceDetail extends React.Component { onClick={() => this.setState({ isOpenNamespaceModal: true })} >{t`Delete namespace`} ) : ( - - Cannot delete namespace until
- collections' dependencies have
- been deleted - - } - position='left' - > - {t`Delete namespace`} -
+ {t`Delete namespace`} )), Date: Wed, 29 Nov 2023 01:37:00 +0000 Subject: [PATCH 06/17] inline tryOpenDeleteModalWithConfirm --- .../namespace-detail/namespace-detail.tsx | 43 ++++++++++------- src/containers/search/search.tsx | 45 +++++++++++------- src/utilities/delete-collection.tsx | 46 ------------------- 3 files changed, 55 insertions(+), 79 deletions(-) diff --git a/src/containers/namespace-detail/namespace-detail.tsx b/src/containers/namespace-detail/namespace-detail.tsx index fc26b513e8..5fed1d6379 100644 --- a/src/containers/namespace-detail/namespace-detail.tsx +++ b/src/containers/namespace-detail/namespace-detail.tsx @@ -999,6 +999,31 @@ export class NamespaceDetail extends React.Component { return; } + const deleteFn = (deleteAll) => () => + DeleteCollectionUtils.countUsedbyDependencies(collection) + .then((count) => { + if (count) { + this.addAlert({ + title: ( + + Cannot delete until collections
+ that depend on this collection
+ have been deleted. +
+ ), + variant: 'warning', + }); + return; + } + + this.setState({ + deleteCollection: collection, + confirmDelete: false, + deleteAll, + }); + }) + .catch((alert) => this.addAlert(alert)); + return { uploadButton: canUpload && (