diff --git a/central/main.go b/central/main.go index 32f9bbe6bb241..3bcb18b908deb 100644 --- a/central/main.go +++ b/central/main.go @@ -246,6 +246,16 @@ func main() { devmode.StartOnDevBuilds("central") log.Infof("Running StackRox Version: %s", pkgVersion.GetMainVersion()) + // TODO: ROX-12750 update with new list of replaced/deprecated resources + log.Warn("The following permission resources have been replaced:\n" + + " Access replaces AuthProvider, Group, Licenses, and User\n" + + " DeploymentExtension replaces Indicator, NetworkBaseline, ProcessWhitelist, and Risk\n" + + " Integration replaces APIToken, BackupPlugins, ImageIntegration, Notifier, and SignatureIntegration\n" + + " Image now also covers ImageComponent\n" + + "The following permission resources will be replaced in the upcoming versions:\n" + + " Administration will replace AllComments, Config, DebugLogs, NetworkGraphConfig, ProbeUpload, ScannerBundle, ScannerDefinitions, SensorUpgradeConfig, and ServiceIdentity\n" + + " Compliance will replace ComplianceRuns\n" + + " Cluster will cover ClusterCVE.") ensureDB(ctx) // Need to remove the backup clone and set the current version diff --git a/ui/apps/platform/cypress/integration/accessControl/accessControlPermissionSets.test.js b/ui/apps/platform/cypress/integration/accessControl/accessControlPermissionSets.test.js index d4ea05292e528..11817c12f295c 100644 --- a/ui/apps/platform/cypress/integration/accessControl/accessControlPermissionSets.test.js +++ b/ui/apps/platform/cypress/integration/accessControl/accessControlPermissionSets.test.js @@ -157,7 +157,7 @@ describe('Access Control Permission sets', () => { $tds.get().forEach((td) => { const resource = td.textContent; // TODO: ROX-12750 Rename DebugLogs to Administration - if (resource === 'DebugLogs') { + if (resource.includes('DebugLogs')) { cy.get(getReadAccessIconForResource(resource)).should( 'have.attr', 'aria-label', @@ -209,7 +209,7 @@ describe('Access Control Permission sets', () => { $tds.get().forEach((td) => { const resource = td.textContent; - if (!resourcesLimited.includes(resource)) { + if (!resourcesLimited.some((v) => resource.includes(v))) { cy.get(getReadAccessIconForResource(resource)).should( 'have.attr', 'aria-label', @@ -315,7 +315,7 @@ describe('Access Control Permission sets', () => { $tds.get().forEach((td) => { const resource = td.textContent; - if (!resourcesLimited.includes(resource)) { + if (!resourcesLimited.some((v) => resource.includes(v))) { cy.get(getReadAccessIconForResource(resource)).should( 'have.attr', 'aria-label', diff --git a/ui/apps/platform/src/Containers/AccessControl/AccessControl.tsx b/ui/apps/platform/src/Containers/AccessControl/AccessControl.tsx index d30d7e011873e..cd13d522096b6 100644 --- a/ui/apps/platform/src/Containers/AccessControl/AccessControl.tsx +++ b/ui/apps/platform/src/Containers/AccessControl/AccessControl.tsx @@ -3,6 +3,7 @@ import { Redirect, Route, Switch } from 'react-router-dom'; import usePermissions from 'hooks/usePermissions'; +import { Alert, List, ListItem } from '@patternfly/react-core'; import { accessControlBasePath, accessControlPath, getEntityPath } from './accessControlPaths'; import AccessControlNoPermission from './AccessControlNoPermission'; @@ -18,11 +19,61 @@ function AccessControl(): ReactElement { // TODO is read access required for all routes in improved Access Control? // TODO Is write access required anywhere in classic Access Control? const { hasReadAccess } = usePermissions(); - const hasReadAccessForAuthProvider = hasReadAccess('Access'); + const hasReadAccessForAccessControlPages = hasReadAccess('Access'); return ( <> - {hasReadAccessForAuthProvider ? ( + +

The following permission resources have been replaced:

+ + + Access replaces{' '} + AuthProvider, Group, Licenses, and User + + + DeploymentExtension replaces{' '} + Indicator, NetworkBaseline, ProcessWhitelist, and Risk + + + Integration replaces{' '} + + APIToken, BackupPlugins, ImageIntegration, Notifier, and + SignatureIntegration + + + + Image now also covers ImageComponent + + + +

+ The following permission resources will be replaced in the upcoming + versions: +

+ + + Administration will replace{' '} + + AllComments, Config, DebugLogs, NetworkGraphConfig, ProbeUpload, + ScannerBundle, ScannerDefinitions, SensorUpgradeConfig, and + ServiceIdentity + + + + Compliance will replace ComplianceRuns + + + Cluster will cover ClusterCVE + + + + } + /> + {hasReadAccessForAccessControlPages ? ( diff --git a/ui/apps/platform/src/Containers/AccessControl/PermissionSets/PermissionsTable.tsx b/ui/apps/platform/src/Containers/AccessControl/PermissionSets/PermissionsTable.tsx index 46e7894f75a2e..6731836fcf25d 100644 --- a/ui/apps/platform/src/Containers/AccessControl/PermissionSets/PermissionsTable.tsx +++ b/ui/apps/platform/src/Containers/AccessControl/PermissionSets/PermissionsTable.tsx @@ -8,7 +8,14 @@ import { PermissionsMap } from 'services/RolesService'; import { ReadAccessIcon, WriteAccessIcon } from './AccessIcons'; import { getReadAccessCount, getWriteAccessCount } from './permissionSets.utils'; -import ResourceDescription from './ResourceDescription'; +import { ResourceDescription } from './ResourceDescription'; +import { + replacedResourceMapping, + resourceRemovalReleaseVersions, + resourceSubstitutions, + deprecatedResourceRowStyle, +} from '../../../constants/accessControl'; +import { ResourceName } from '../../../types/roleResources'; export type PermissionsTableProps = { resourceToAccess: PermissionsMap; @@ -51,8 +58,41 @@ function PermissionsTable({ {resourceToAccessEntries.map(([resource, accessLevel]) => ( - - {resource} + + +

{resource}

+

+ {resourceSubstitutions[resource] && ( + <>Replaces {resourceSubstitutions[resource].join(', ')} + )} +

+

+ {resourceRemovalReleaseVersions.has(resource as ResourceName) && ( + <> + Will be removed in{' '} + {resourceRemovalReleaseVersions.get( + resource as ResourceName + )} + . + + )} +

+

+ {replacedResourceMapping.has(resource as ResourceName) && ( + <> + Will be replaced by{' '} + {replacedResourceMapping.get(resource as ResourceName)}. + + )} +

+ diff --git a/ui/apps/platform/src/Containers/AccessControl/PermissionSets/ResourceDescription.tsx b/ui/apps/platform/src/Containers/AccessControl/PermissionSets/ResourceDescription.tsx index 233191c38288d..f9aec7211ba49 100644 --- a/ui/apps/platform/src/Containers/AccessControl/PermissionSets/ResourceDescription.tsx +++ b/ui/apps/platform/src/Containers/AccessControl/PermissionSets/ResourceDescription.tsx @@ -11,6 +11,7 @@ const resourceDescriptions: Record = { Alert: 'Read: View policy violations. Write: Resolve or edit policy violations.', CVE: 'Internal use only', Cluster: 'Read: View secured clusters. Write: Add, modify, or delete secured clusters.', + ClusterCVE: 'Internal use only', Compliance: 'Read: View compliance standards, results, and runs. Write: Add, modify, or delete scheduled compliance runs.', Deployment: 'Read: View deployments (workloads) in secured clusters. Write: N/A', @@ -72,7 +73,7 @@ export type ResourceDescriptionProps = { resource: string; }; -function ResourceDescription({ resource }: ResourceDescriptionProps): ReactElement { +export function ResourceDescription({ resource }: ResourceDescriptionProps): ReactElement { // The description becomes the prop for possible future request from backend. const description = resourceDescriptions[resource] ?? ''; const readIndex = description.indexOf('Read: '); diff --git a/ui/apps/platform/src/Containers/Integrations/IntegrationsNoPermission.tsx b/ui/apps/platform/src/Containers/Integrations/IntegrationsNoPermission.tsx new file mode 100644 index 0000000000000..46ceeab8ec1bf --- /dev/null +++ b/ui/apps/platform/src/Containers/Integrations/IntegrationsNoPermission.tsx @@ -0,0 +1,30 @@ +import React, { ReactElement } from 'react'; +import { + Alert, + AlertVariant, + Divider, + PageSection, + PageSectionVariants, + Title, +} from '@patternfly/react-core'; + +function IntegrationsNoPermission(): ReactElement { + return ( + <> + + Integrations + + + + + + + ); +} + +export default IntegrationsNoPermission; diff --git a/ui/apps/platform/src/Containers/Integrations/IntegrationsPage.tsx b/ui/apps/platform/src/Containers/Integrations/IntegrationsPage.tsx index 99928896f8380..78549c8bed492 100644 --- a/ui/apps/platform/src/Containers/Integrations/IntegrationsPage.tsx +++ b/ui/apps/platform/src/Containers/Integrations/IntegrationsPage.tsx @@ -15,16 +15,28 @@ import IntegrationsListPage from './IntegrationsListPage'; import CreateIntegrationPage from './CreateIntegrationPage'; import EditIntegrationPage from './EditIntegrationPage'; import IntegrationDetailsPage from './IntegrationDetailsPage'; +import usePermissions from '../../hooks/usePermissions'; +import IntegrationsNoPermission from './IntegrationsNoPermission'; -const Page = (): ReactElement => ( - - - - - - - - -); +const Page = (): ReactElement => { + const { hasReadAccess } = usePermissions(); + const hasReadAccessForIntegrations = hasReadAccess('Integration'); + return ( + <> + {hasReadAccessForIntegrations ? ( + + + + + + + + + ) : ( + + )} + + ); +}; export default Page; diff --git a/ui/apps/platform/src/Containers/User/UserPermissionsTable.tsx b/ui/apps/platform/src/Containers/User/UserPermissionsTable.tsx index 0b078cc18520d..cce57aa660b8f 100644 --- a/ui/apps/platform/src/Containers/User/UserPermissionsTable.tsx +++ b/ui/apps/platform/src/Containers/User/UserPermissionsTable.tsx @@ -7,6 +7,11 @@ import { ReadAccessIcon, WriteAccessIcon, } from 'Containers/AccessControl/PermissionSets/AccessIcons'; +import { + deprecatedResourceRowStyle, + resourceRemovalReleaseVersions, +} from '../../constants/accessControl'; +import { ResourceName } from '../../types/roleResources'; export type UserPermissionsTableProps = { permissions: PermissionsMap; @@ -24,7 +29,14 @@ function UserPermissionsTable({ permissions }: UserPermissionsTableProps): React {Object.entries(permissions).map(([resource, accessLevel]) => ( - + {resource} diff --git a/ui/apps/platform/src/constants/accessControl.ts b/ui/apps/platform/src/constants/accessControl.ts index adccf532ecbe0..64a670400c0c1 100644 --- a/ui/apps/platform/src/constants/accessControl.ts +++ b/ui/apps/platform/src/constants/accessControl.ts @@ -1,4 +1,6 @@ /* constants specific to Roles */ +import { ResourceName } from '../types/roleResources'; + export const NO_ACCESS = 'NO_ACCESS'; export const READ_ACCESS = 'READ_ACCESS'; export const READ_WRITE_ACCESS = 'READ_WRITE_ACCESS'; @@ -86,3 +88,51 @@ export const defaultSelectedRole = { name: '', resourceToAccess: defaultNewRolePermissions, }; + +// TODO: ROX-12750 update with new list of replaced/deprecated resources +export const resourceSubstitutions: Record = { + Access: ['AuthProvider', 'Group', 'Licenses', 'User'], + DeploymentExtension: ['Indicator', 'NetworkBaseline', 'ProcessWhitelist', 'Risk'], + Integration: [ + 'APIToken', + 'BackupPlugins', + 'ImageIntegration', + 'Notifier', + 'SignatureIntegration', + ], + Image: ['ImageComponent'], +}; + +// TODO: ROX-12750 update with new list of replaced/deprecated resources +export const resourceRemovalReleaseVersions = new Map([ + ['AllComments', '3.75'], + ['ComplianceRuns', '3.75'], + ['Config', '3.75'], + ['DebugLogs', '3.75'], + ['NetworkGraphConfig', '3.75'], + ['ProbeUpload', '3.75'], + ['ScannerBundle', '3.75'], + ['ScannerDefinitions', '3.75'], + ['SensorUpgradeConfig', '3.75'], + ['ServiceIdentity', '3.75'], + ['ClusterCVE', '3.74'], +]); + +// TODO(ROX-11453): Remove this mapping once the old resources are fully deprecated. +export const replacedResourceMapping = new Map([ + // TODO: ROX-12750 Remove AllComments, ComplianceRunSchedule, ComplianceRuns, Config, DebugLogs, + // NetworkGraphConfig, ProbeUpload, ScannerBundle, ScannerDefinitions, SensorUpgradeConfig and ServiceIdentity. + ['AllComments', 'Administration'], + ['ComplianceRuns', 'Compliance'], + ['Config', 'Administration'], + ['DebugLogs', 'Administration'], + ['NetworkGraphConfig', 'Administration'], + ['ProbeUpload', 'Administration'], + ['ScannerBundle', 'Administration'], + ['ScannerDefinitions', 'Administration'], + ['SensorUpgradeConfig', 'Administration'], + ['ServiceIdentity', 'Administration'], + ['ClusterCVE', 'Cluster'], +]); + +export const deprecatedResourceRowStyle = { backgroundColor: 'rgb(255,250,205)' }; diff --git a/ui/apps/platform/src/hooks/usePermissions.ts b/ui/apps/platform/src/hooks/usePermissions.ts index 8e878e8adffb4..24fc48d39f375 100644 --- a/ui/apps/platform/src/hooks/usePermissions.ts +++ b/ui/apps/platform/src/hooks/usePermissions.ts @@ -4,6 +4,7 @@ import { createStructuredSelector } from 'reselect'; import { selectors } from 'reducers'; import { Access } from 'types/role.proto'; import { ResourceName } from 'types/roleResources'; +import { replacedResourceMapping } from 'constants/accessControl'; export type HasNoAccess = (resourceName: ResourceName) => boolean; export type HasReadAccess = (resourceName: ResourceName) => boolean; @@ -24,22 +25,6 @@ const stateSelector = createStructuredSelector<{ isLoadingPermissions: selectors.getIsLoadingUserRolePermissions, }); -// TODO(ROX-11453): Remove this mapping once the old resources are fully deprecated. -const replacedResourceMapping = new Map([ - // TODO: ROX-12750 Remove AllComments, ComplianceRunSchedule, ComplianceRuns, Config, DebugLogs, - // NetworkGraphConfig, ProbeUpload, ScannerBundle, ScannerDefinitions, SensorUpgradeConfig and ServiceIdentity. - ['AllComments', 'Administration'], - ['ComplianceRuns', 'Compliance'], - ['Config', 'Administration'], - ['DebugLogs', 'Administration'], - ['NetworkGraphConfig', 'Administration'], - ['ProbeUpload', 'Administration'], - ['ScannerBundle', 'Administration'], - ['ScannerDefinitions', 'Administration'], - ['SensorUpgradeConfig', 'Administration'], - ['ServiceIdentity', 'Administration'], -]); - const usePermissions = (): UsePermissionsResponse => { const { userRolePermissions, isLoadingPermissions } = useSelector(stateSelector); diff --git a/ui/apps/platform/src/reducers/roles.js b/ui/apps/platform/src/reducers/roles.js index 287f95752a317..b7202d01649ae 100644 --- a/ui/apps/platform/src/reducers/roles.js +++ b/ui/apps/platform/src/reducers/roles.js @@ -2,6 +2,7 @@ import { combineReducers } from 'redux'; import isEqual from 'lodash/isEqual'; import { createFetchingActionTypes, createFetchingActions } from 'utils/fetchingReduxRoutines'; +import { replacedResourceMapping } from '../constants/accessControl'; export const ACCESS_LEVEL = Object.freeze({ READ_WRITE_ACCESS: 'READ_WRITE_ACCESS', @@ -104,22 +105,6 @@ const getUserRolePermissions = (state) => state.userRolePermissions; const getUserRolePermissionsError = (state) => state.error; const getIsLoadingUserRolePermissions = (state) => state.isLoading; -// TODO(ROX-11453): Remove this mapping once the old resources are fully deprecated. -const replacedResourceMapping = new Map([ - // TODO: ROX-12750 Remove AllComments, ComplianceRunSchedule, ComplianceRuns, Config, DebugLogs, - // NetworkGraphConfig, ProbeUpload, ScannerBundle, ScannerDefinitions, SensorUpgradeConfig and ServiceIdentity. - ['AllComments', 'Administration'], - ['ComplianceRuns', 'Compliance'], - ['Config', 'Administration'], - ['DebugLogs', 'Administration'], - ['NetworkGraphConfig', 'Administration'], - ['ProbeUpload', 'Administration'], - ['ScannerBundle', 'Administration'], - ['ScannerDefinitions', 'Administration'], - ['SensorUpgradeConfig', 'Administration'], - ['ServiceIdentity', 'Administration'], -]); - /* * Given resource string (for example, "Integration") and role or permissionSet object, * return access level (for example, "READ_ACCESS"). diff --git a/ui/apps/platform/src/types/roleResources.ts b/ui/apps/platform/src/types/roleResources.ts index d03877436b5f1..0a46c2c4c9341 100644 --- a/ui/apps/platform/src/types/roleResources.ts +++ b/ui/apps/platform/src/types/roleResources.ts @@ -40,6 +40,7 @@ export type ResourceName = // TODO: ROX-12750 Remove AllComments, ComplianceRunSchedule, ComplianceRuns, Config, DebugLogs, // NetworkGraphConfig, ProbeUpload, ScannerBundle, ScannerDefinitions, SensorUpgradeConfig and ServiceIdentity. | 'AllComments' + | 'ClusterCVE' | 'ComplianceRuns' | 'Config' | 'DebugLogs'