From 30fc22d79bddd488009edb17ca22b4434b6b82fa Mon Sep 17 00:00:00 2001 From: Tiago Fonseca Date: Thu, 8 May 2025 20:47:41 -0300 Subject: [PATCH 1/3] fix: permissions not revoked after view project disabled Ref: #5432 --- frontend/common/utils/utils.tsx | 6 +- frontend/web/components/EditPermissions.tsx | 213 ++++++++++++++------ 2 files changed, 159 insertions(+), 60 deletions(-) diff --git a/frontend/common/utils/utils.tsx b/frontend/common/utils/utils.tsx index 5654a39e24fa..e32c9711e99a 100644 --- a/frontend/common/utils/utils.tsx +++ b/frontend/common/utils/utils.tsx @@ -109,6 +109,11 @@ const Utils = Object.assign({}, require('./base/_utils'), { ) }, + capitalize(str: string) { + if (!str) return + return str.charAt(0).toUpperCase() + str.slice(1) + }, + changeRequestsEnabled(value: number | null | undefined) { return typeof value === 'number' }, @@ -132,7 +137,6 @@ const Utils = Object.assign({}, require('./base/_utils'), { if (value > 0.002) return 'HIGH' as PConfidence return 'VERY_HIGH' as PConfidence }, - copyToClipboard: async ( value: string, successMessage?: string, diff --git a/frontend/web/components/EditPermissions.tsx b/frontend/web/components/EditPermissions.tsx index ceb115e5163a..35e33c1021e8 100644 --- a/frontend/web/components/EditPermissions.tsx +++ b/frontend/web/components/EditPermissions.tsx @@ -61,11 +61,11 @@ import classNames from 'classnames' import OrganisationProvider from 'common/providers/OrganisationProvider' import { useHasPermission } from 'common/providers/Permission' import PlanBasedAccess from './PlanBasedAccess' -import { useGetTagsQuery } from 'common/services/useTag' import { components } from 'react-select' import { SingleValueProps } from 'react-select/lib/components/SingleValue' import AddEditTags from './tags/AddEditTags' import { RouterChildContext } from 'react-router' +import Utils from 'common/utils/utils' const Project = require('common/project') @@ -159,6 +159,46 @@ const withAdminPermissions = (InnerComponent: any) => { } return WrappedComponent } + +type RemoveViewPermissionModalProps = { + level: string + onConfirm: () => void + onCancel: () => void +} + +const RemoveViewPermissionModal = ({ + level, + onCancel, + onConfirm, +}: RemoveViewPermissionModalProps) => { + return ( +
+
+ Removing view {level} permission will remove all other user + permissions for this {level}. +
+
+ + +
+
+ ) +} + const _EditPermissionsModal: FC = withAdminPermissions( forwardRef((props: EditPermissionModalType) => { const { @@ -199,11 +239,6 @@ const _EditPermissionsModal: FC = withAdminPermissions( ? props.parentId : undefined - const { data: tags, isLoading: tagsLoading } = useGetTagsQuery( - { projectId: `${projectId}` }, - { skip: !projectId }, - ) - const [permissionWasCreated, setPermissionWasCreated] = useState(false) const [rolesSelected, setRolesSelected] = useState< @@ -260,29 +295,52 @@ const _EditPermissionsModal: FC = withAdminPermissions( }, [groupWithRolesDataSuccesfull]) const processResults = (results: (UserPermission | GroupPermission)[]) => { + const findPermissionByGroup = () => { + return find( + results || [], + (r) => (r as GroupPermission).group.id === group?.id, + ) + } + + const findPermissionByRole = () => { + return find( + results || [], + (r) => (r as GroupPermission).role === role?.id, + ) + } + + const findPermissionByUser = () => { + return find( + results || [], + (r) => (r as UserPermission).user?.id === user?.id, + ) + } + const foundPermission = isGroup - ? find( - results || [], - (r) => (r as GroupPermission).group.id === group?.id, - ) + ? findPermissionByGroup() : role - ? find(results || [], (r) => (r as GroupPermission).role === role?.id) - : find( - results || [], - (r) => (r as UserPermission).user?.id === user?.id, - ) - const permissions = - (role && (level === 'project' || level === 'environment') - ? foundPermission?.permissions - : (foundPermission?.permissions || []).map((v) => ({ - permission_key: v, - tags: [], - }))) || [] + ? findPermissionByRole() + : findPermissionByUser() + + const isProjectOrEnvironmentRole = + role && (level === 'project' || level === 'environment') + + const processPermissions = () => { + if (isProjectOrEnvironmentRole) { + return foundPermission?.permissions || [] + } + + return (foundPermission?.permissions || []).map((v) => ({ + permission_key: v, + tags: [], + })) + } + return { ...(foundPermission || {}), group: group?.id, //Since role permissions and other permissions are different in data structure, adjust permissions to match - permissions, + permissions: processPermissions(), user: user?.id, } as EntityPermissions } @@ -464,7 +522,7 @@ const _EditPermissionsModal: FC = withAdminPermissions( }) } //eslint-disable-next-line - }, []) + }, []) const admin = () => entityPermissions && entityPermissions.admin @@ -490,22 +548,32 @@ const _EditPermissionsModal: FC = withAdminPermissions( return 'GRANTED' } + const requiresViewPermission = (permissionKey: string) => { + return ( + level !== 'organisation' && + permissionKey !== `VIEW_${level.toUpperCase()}` + ) + } + const save = useCallback(() => { - const entityId = - typeof entityPermissions.id === 'undefined' ? '' : entityPermissions.id + const entityId = entityPermissions.id ?? '' setValueChanged(false) if (!role) { const url = isGroup ? `${level}s/${id}/user-group-permissions/${entityId}` : `${level}s/${id}/user-permissions/${entityId}` + + const permissions = entityPermissions.permissions.map( + (v) => v.permission_key, + ) + const payload = { + ...entityPermissions, + permissions, + } + setSaving(true) const action = entityId ? 'put' : 'post' - _data[action](`${Project.api}${url}${entityId && '/'}`, { - ...entityPermissions, - permissions: entityPermissions.permissions.map( - (v) => v.permission_key, - ), - }) + _data[action](`${Project.api}${url}${entityId && '/'}`, payload) .then( ( res: Omit & { @@ -519,11 +587,7 @@ const _EditPermissionsModal: FC = withAdminPermissions( tags: [], })), }) - toast( - `${ - level.charAt(0).toUpperCase() + level.slice(1) - } Permissions Saved`, - ) + toast(`${Utils.capitalize(level)} Permissions Saved`) onSave?.() }, ) @@ -640,26 +704,31 @@ const _EditPermissionsModal: FC = withAdminPermissions( updatedPermissions.splice(index, 1) } - setEntityPermissions({ + return setEntityPermissions({ ...entityPermissions, permissions: updatedPermissions, }) - } else { - const newEntityPermissions = { ...entityPermissions } + } - const index = newEntityPermissions.permissions.findIndex( - (v) => v.permission_key === key, - ) - if (index === -1) { - newEntityPermissions.permissions.push({ - permission_key: key, - tags: [], - }) - } else { - newEntityPermissions.permissions.splice(index, 1) - } - setEntityPermissions(newEntityPermissions) + const newEntityPermissions = { ...entityPermissions } + + const index = newEntityPermissions.permissions.findIndex( + (v) => v.permission_key === key, + ) + if (index === -1) { + newEntityPermissions.permissions.push({ + permission_key: key, + tags: [], + }) + } else if ( + level !== 'organisation' && + key === `VIEW_${level.toUpperCase()}` + ) { + newEntityPermissions.permissions = [] + } else { + newEntityPermissions.permissions.splice(index, 1) } + setEntityPermissions(newEntityPermissions) } const toggleAdmin = () => { @@ -855,10 +924,11 @@ const _EditPermissionsModal: FC = withAdminPermissions( items={permissions} renderRow={(p) => { const levelUpperCase = level.toUpperCase() - const disabled = - level !== 'organisation' && - p.key !== `VIEW_${levelUpperCase}` && - !hasPermission(`VIEW_${levelUpperCase}`) + const viewPermission = `VIEW_${levelUpperCase}` + const isPermissionDisabled = + requiresViewPermission(p.key) && + !hasPermission(viewPermission) + const permission = entityPermissions.permissions.find( (v) => v.permission_key === p.key, ) @@ -897,7 +967,7 @@ const _EditPermissionsModal: FC = withAdminPermissions( selectPermissions(p.key, v.value) }} className='react-select select-sm' - disabled={disabled || admin() || saving} + disabled={isPermissionDisabled || admin() || saving} options={ p.supports_tag ? permissionOptions @@ -912,11 +982,36 @@ const _EditPermissionsModal: FC = withAdminPermissions( { + if ( + level !== 'organisation' && + p.key === viewPermission && + hasPermission(viewPermission) && + entityPermissions.permissions.length > 1 + ) { + return openModal2( + `Remove View ${Utils.capitalize( + level, + )} Permission`, + { + setValueChanged(true) + togglePermission(p.key) + }} + onCancel={() => { + closeModal2() + }} + />, + ) + } + setValueChanged(true) togglePermission(p.key) }} - disabled={disabled || admin() || saving} - checked={!disabled && hasPermission(p.key)} + disabled={isPermissionDisabled || admin() || saving} + checked={ + !isPermissionDisabled && hasPermission(p.key) + } /> )} From d422a967c0909db7188c69c06e1d4dd86c9263bc Mon Sep 17 00:00:00 2001 From: Tiago Fonseca Date: Thu, 8 May 2025 20:49:39 -0300 Subject: [PATCH 2/3] close model on confirm --- frontend/web/components/EditPermissions.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/web/components/EditPermissions.tsx b/frontend/web/components/EditPermissions.tsx index 35e33c1021e8..6eef4b0f5fa1 100644 --- a/frontend/web/components/EditPermissions.tsx +++ b/frontend/web/components/EditPermissions.tsx @@ -997,6 +997,7 @@ const _EditPermissionsModal: FC = withAdminPermissions( onConfirm={() => { setValueChanged(true) togglePermission(p.key) + closeModal2() }} onCancel={() => { closeModal2() From a2c1485b7c93874e74236c4eeaec5203142e9a9b Mon Sep 17 00:00:00 2001 From: Tiago Paiva Date: Fri, 9 May 2025 09:23:05 -0300 Subject: [PATCH 3/3] Update frontend/common/utils/utils.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- frontend/common/utils/utils.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/common/utils/utils.tsx b/frontend/common/utils/utils.tsx index e32c9711e99a..1e290c85444a 100644 --- a/frontend/common/utils/utils.tsx +++ b/frontend/common/utils/utils.tsx @@ -110,7 +110,7 @@ const Utils = Object.assign({}, require('./base/_utils'), { }, capitalize(str: string) { - if (!str) return + if (!str) return "" return str.charAt(0).toUpperCase() + str.slice(1) },