diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/empty/policy_trusted_apps_empty_unassigned.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/empty/policy_trusted_apps_empty_unassigned.tsx index 0ccdf9bcb388d..ee52e1210a481 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/empty/policy_trusted_apps_empty_unassigned.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/empty/policy_trusted_apps_empty_unassigned.tsx @@ -10,6 +10,7 @@ import { EuiEmptyPrompt, EuiButton, EuiPageTemplate, EuiLink } from '@elastic/eu import { FormattedMessage } from '@kbn/i18n/react'; import { usePolicyDetailsNavigateCallback } from '../../policy_hooks'; import { useGetLinkTo } from './use_policy_trusted_apps_empty_hooks'; +import { useEndpointPrivileges } from '../../../../../../common/components/user_privileges/use_endpoint_privileges'; interface CommonProps { policyId: string; @@ -17,6 +18,7 @@ interface CommonProps { } export const PolicyTrustedAppsEmptyUnassigned = memo(({ policyId, policyName }) => { + const { isPlatinumPlus } = useEndpointPrivileges(); const navigateCallback = usePolicyDetailsNavigateCallback(); const { onClickHandler, toRouteUrl } = useGetLinkTo(policyId, policyName); const onClickPrimaryButtonHandler = useCallback( @@ -47,12 +49,21 @@ export const PolicyTrustedAppsEmptyUnassigned = memo(({ policyId, p /> } actions={[ - - - , + ...(isPlatinumPlus + ? [ + + + , + ] + : []), // eslint-disable-next-line @elastic/eui/href-or-on-click { title={ } /> diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.test.tsx index 5d5d36d41aaf8..8ae0d9d45c236 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.test.tsx @@ -19,8 +19,20 @@ import { createLoadedResourceState, isLoadedResourceState } from '../../../../.. import { getPolicyDetailsArtifactsListPath } from '../../../../../common/routing'; import { EndpointDocGenerator } from '../../../../../../../common/endpoint/generate_data'; import { policyListApiPathHandlers } from '../../../store/test_mock_utils'; +import { licenseService } from '../../../../../../common/hooks/use_license'; jest.mock('../../../../trusted_apps/service'); +jest.mock('../../../../../../common/hooks/use_license', () => { + const licenseServiceInstance = { + isPlatinumPlus: jest.fn(), + }; + return { + licenseService: licenseServiceInstance, + useLicense: () => { + return licenseServiceInstance; + }, + }; +}); let mockedContext: AppContextTestRender; let waitForAction: MiddlewareActionSpyHelper['waitForAction']; @@ -106,4 +118,31 @@ describe('Policy trusted apps layout', () => { expect(component.getByTestId('policyDetailsTrustedAppsCount')).not.toBeNull(); }); + + it('should hide assign button on empty state with unassigned policies when downgraded to a gold or below license', async () => { + (licenseService.isPlatinumPlus as jest.Mock).mockReturnValue(false); + const component = render(); + mockedContext.history.push(getPolicyDetailsArtifactsListPath('1234')); + + await waitForAction('assignedTrustedAppsListStateChanged'); + + mockedContext.store.dispatch({ + type: 'policyArtifactsDeosAnyTrustedAppExists', + payload: createLoadedResourceState(true), + }); + expect(component.queryByTestId('assign-ta-button')).toBeNull(); + }); + it('should hide the `Assign trusted applications` button when there is data and the license is downgraded to gold or below', async () => { + (licenseService.isPlatinumPlus as jest.Mock).mockReturnValue(false); + TrustedAppsHttpServiceMock.mockImplementation(() => { + return { + getTrustedAppsList: () => getMockListResponse(), + }; + }); + const component = render(); + mockedContext.history.push(getPolicyDetailsArtifactsListPath('1234')); + + await waitForAction('assignedTrustedAppsListStateChanged'); + expect(component.queryByTestId('assignTrustedAppButton')).toBeNull(); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.tsx index 64e40e330ad2b..2421602f4e5af 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.tsx @@ -25,6 +25,7 @@ import { import { usePolicyDetailsNavigateCallback, usePolicyDetailsSelector } from '../../policy_hooks'; import { PolicyTrustedAppsFlyout } from '../flyout'; import { PolicyTrustedAppsList } from '../list/policy_trusted_apps_list'; +import { useEndpointPrivileges } from '../../../../../../common/components/user_privileges/use_endpoint_privileges'; export const PolicyTrustedAppsLayout = React.memo(() => { const location = usePolicyDetailsSelector(getCurrentArtifactsLocation); @@ -33,6 +34,7 @@ export const PolicyTrustedAppsLayout = React.memo(() => { const policyItem = usePolicyDetailsSelector(policyDetails); const navigateCallback = usePolicyDetailsNavigateCallback(); const hasAssignedTrustedApps = usePolicyDetailsSelector(doesPolicyHaveTrustedApps); + const { isPlatinumPlus } = useEndpointPrivileges(); const showListFlyout = location.show === 'list'; @@ -41,6 +43,7 @@ export const PolicyTrustedAppsLayout = React.memo(() => { navigateCallback({ show: 'list', @@ -88,7 +91,7 @@ export const PolicyTrustedAppsLayout = React.memo(() => { - {assignTrustedAppButton} + {isPlatinumPlus && assignTrustedAppButton} ) : null} { )} - {showListFlyout ? : null} + {isPlatinumPlus && showListFlyout ? : null} ) : null; }); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.test.tsx index ff94e3befe8c8..316b70064d9db 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.test.tsx @@ -21,6 +21,13 @@ import { } from '../../../../../state'; import { fireEvent, within, act, waitFor } from '@testing-library/react'; import { APP_ID } from '../../../../../../../common/constants'; +import { + EndpointPrivileges, + useEndpointPrivileges, +} from '../../../../../../common/components/user_privileges/use_endpoint_privileges'; + +jest.mock('../../../../../../common/components/user_privileges/use_endpoint_privileges'); +const mockUseEndpointPrivileges = useEndpointPrivileges as jest.Mock; describe('when rendering the PolicyTrustedAppsList', () => { // The index (zero based) of the card created by the generator that is policy specific @@ -32,6 +39,16 @@ describe('when rendering the PolicyTrustedAppsList', () => { let mockedApis: ReturnType; let waitForAction: AppContextTestRender['middlewareSpy']['waitForAction']; + const loadedUserEndpointPrivilegesState = ( + endpointOverrides: Partial = {} + ): EndpointPrivileges => ({ + loading: false, + canAccessFleet: true, + canAccessEndpointManagement: true, + isPlatinumPlus: true, + ...endpointOverrides, + }); + const getCardByIndexPosition = (cardIndex: number = 0) => { const card = renderResult.getAllByTestId('policyTrustedAppsGrid-card')[cardIndex]; @@ -66,8 +83,12 @@ describe('when rendering the PolicyTrustedAppsList', () => { ); }; + afterAll(() => { + mockUseEndpointPrivileges.mockReset(); + }); beforeEach(() => { appTestContext = createAppRootMockRenderer(); + mockUseEndpointPrivileges.mockReturnValue(loadedUserEndpointPrivilegesState()); mockedApis = policyDetailsPageAllApiHttpMocks(appTestContext.coreStart.http); appTestContext.setExperimentalFlag({ trustedAppsByPolicyEnabled: true }); @@ -297,4 +318,16 @@ describe('when rendering the PolicyTrustedAppsList', () => { }) ); }); + + it('does not show remove option in actions menu if license is downgraded to gold or below', async () => { + await render(); + mockUseEndpointPrivileges.mockReturnValue( + loadedUserEndpointPrivilegesState({ + isPlatinumPlus: false, + }) + ); + await toggleCardActionMenu(POLICY_SPECIFIC_CARD_INDEX); + + expect(renderResult.queryByTestId('policyTrustedAppsGrid-removeAction')).toBeNull(); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.tsx index 5d6c9731c7070..8ab2f5fd465e0 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.tsx @@ -38,6 +38,7 @@ import { ContextMenuItemNavByRouterProps } from '../../../../../components/conte import { ArtifactEntryCollapsibleCardProps } from '../../../../../components/artifact_entry_card'; import { useTestIdGenerator } from '../../../../../components/hooks/use_test_id_generator'; import { RemoveTrustedAppFromPolicyModal } from './remove_trusted_app_from_policy_modal'; +import { useEndpointPrivileges } from '../../../../../../common/components/user_privileges/use_endpoint_privileges'; const DATA_TEST_SUBJ = 'policyTrustedAppsGrid'; @@ -46,6 +47,7 @@ export const PolicyTrustedAppsList = memo(() => { const toasts = useToasts(); const history = useHistory(); const { getAppUrl } = useAppUrl(); + const { isPlatinumPlus } = useEndpointPrivileges(); const policyId = usePolicyDetailsSelector(policyIdFromParams); const hasTrustedApps = usePolicyDetailsSelector(doesPolicyHaveTrustedApps); const isLoading = usePolicyDetailsSelector(isPolicyTrustedAppListLoading); @@ -132,44 +134,50 @@ export const PolicyTrustedAppsList = memo(() => { return byIdPolicies; }, {}); + const fullDetailsAction: ArtifactCardGridCardComponentProps['actions'] = [ + { + icon: 'controlsHorizontal', + children: i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedApps.list.viewAction', + { defaultMessage: 'View full details' } + ), + href: getAppUrl({ appId: APP_ID, path: viewUrlPath }), + navigateAppId: APP_ID, + navigateOptions: { path: viewUrlPath }, + 'data-test-subj': getTestId('viewFullDetailsAction'), + }, + ]; const thisTrustedAppCardProps: ArtifactCardGridCardComponentProps = { expanded: Boolean(isCardExpanded[trustedApp.id]), - actions: [ - { - icon: 'controlsHorizontal', - children: i18n.translate( - 'xpack.securitySolution.endpoint.policy.trustedApps.list.viewAction', - { defaultMessage: 'View full details' } - ), - href: getAppUrl({ appId: APP_ID, path: viewUrlPath }), - navigateAppId: APP_ID, - navigateOptions: { path: viewUrlPath }, - 'data-test-subj': getTestId('viewFullDetailsAction'), - }, - { - icon: 'trash', - children: i18n.translate( - 'xpack.securitySolution.endpoint.policy.trustedApps.list.removeAction', - { defaultMessage: 'Remove from policy' } - ), - onClick: () => { - setTrustedAppsForRemoval([trustedApp]); - setShowRemovalModal(true); - }, - disabled: isGlobal, - toolTipContent: isGlobal - ? i18n.translate( - 'xpack.securitySolution.endpoint.policy.trustedApps.list.removeActionNotAllowed', - { - defaultMessage: - 'Globally applied trusted applications cannot be removed from policy.', - } - ) - : undefined, - toolTipPosition: 'top', - 'data-test-subj': getTestId('removeAction'), - }, - ], + actions: isPlatinumPlus + ? [ + ...fullDetailsAction, + { + icon: 'trash', + children: i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedApps.list.removeAction', + { defaultMessage: 'Remove from policy' } + ), + onClick: () => { + setTrustedAppsForRemoval([trustedApp]); + setShowRemovalModal(true); + }, + disabled: isGlobal, + toolTipContent: isGlobal + ? i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedApps.list.removeActionNotAllowed', + { + defaultMessage: + 'Globally applied trusted applications cannot be removed from policy.', + } + ) + : undefined, + toolTipPosition: 'top', + 'data-test-subj': getTestId('removeAction'), + }, + ] + : fullDetailsAction, + policies: assignedPoliciesMenuItems, }; @@ -177,7 +185,7 @@ export const PolicyTrustedAppsList = memo(() => { } return newCardProps; - }, [allPoliciesById, getAppUrl, getTestId, isCardExpanded, trustedAppItems]); + }, [allPoliciesById, getAppUrl, getTestId, isCardExpanded, trustedAppItems, isPlatinumPlus]); const provideCardProps = useCallback['cardComponentProps']>( (item) => {