diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.ts b/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.ts index a5476159a03ac..317ee194ccc20 100644 --- a/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.ts +++ b/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.ts @@ -8,7 +8,7 @@ import { schema } from '@kbn/config-schema'; import { ConditionEntryField, OperatingSystem } from '@kbn/securitysolution-utils'; import { TrustedAppConditionEntry } from '../types'; -import { getDuplicateFields, isValidHash } from '../service/trusted_apps/validations'; +import { getDuplicateFields, isValidHash } from '../service/artifacts/validations'; export const DeleteTrustedAppsRequestSchema = { params: schema.object({ diff --git a/x-pack/plugins/security_solution/common/endpoint/service/trusted_apps/validations.ts b/x-pack/plugins/security_solution/common/endpoint/service/artifacts/validations.ts similarity index 100% rename from x-pack/plugins/security_solution/common/endpoint/service/trusted_apps/validations.ts rename to x-pack/plugins/security_solution/common/endpoint/service/artifacts/validations.ts diff --git a/x-pack/plugins/security_solution/public/common/store/actions.ts b/x-pack/plugins/security_solution/public/common/store/actions.ts index 1987edc0e7307..585fdb98a0323 100644 --- a/x-pack/plugins/security_solution/public/common/store/actions.ts +++ b/x-pack/plugins/security_solution/public/common/store/actions.ts @@ -7,7 +7,6 @@ import { EndpointAction } from '../../management/pages/endpoint_hosts/store/action'; import { PolicyDetailsAction } from '../../management/pages/policy/store/policy_details'; -import { TrustedAppsPageAction } from '../../management/pages/trusted_apps/store/action'; import { EventFiltersPageAction } from '../../management/pages/event_filters/store/action'; export { appActions } from './app'; @@ -20,5 +19,4 @@ export type AppAction = | EndpointAction | RoutingAction | PolicyDetailsAction - | TrustedAppsPageAction | EventFiltersPageAction; diff --git a/x-pack/plugins/security_solution/public/management/common/routing.test.ts b/x-pack/plugins/security_solution/public/management/common/routing.test.ts deleted file mode 100644 index 99afd30688983..0000000000000 --- a/x-pack/plugins/security_solution/public/management/common/routing.test.ts +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { extractTrustedAppsListPageLocation, getTrustedAppsListPath } from './routing'; -import { MANAGEMENT_DEFAULT_PAGE, MANAGEMENT_DEFAULT_PAGE_SIZE } from './constants'; -import { TrustedAppsListPageLocation } from '../pages/trusted_apps/state'; - -describe('routing', () => { - describe('extractListPaginationParams()', () => { - it('extracts default page index when not provided', () => { - expect(extractTrustedAppsListPageLocation({}).page_index).toBe(MANAGEMENT_DEFAULT_PAGE); - }); - - it('extracts default page index when too small value provided', () => { - expect(extractTrustedAppsListPageLocation({ page_index: '-1' }).page_index).toBe( - MANAGEMENT_DEFAULT_PAGE - ); - }); - - it('extracts default page index when not a number provided', () => { - expect(extractTrustedAppsListPageLocation({ page_index: 'a' }).page_index).toBe( - MANAGEMENT_DEFAULT_PAGE - ); - }); - - it('extracts only last page index when multiple values provided', () => { - expect(extractTrustedAppsListPageLocation({ page_index: ['1', '2'] }).page_index).toBe(2); - }); - - it('extracts proper page index when single valid value provided', () => { - expect(extractTrustedAppsListPageLocation({ page_index: '2' }).page_index).toBe(2); - }); - - it('extracts default page size when not provided', () => { - expect(extractTrustedAppsListPageLocation({}).page_size).toBe(MANAGEMENT_DEFAULT_PAGE_SIZE); - }); - - it('extracts default page size when invalid option provided', () => { - expect(extractTrustedAppsListPageLocation({ page_size: '25' }).page_size).toBe( - MANAGEMENT_DEFAULT_PAGE_SIZE - ); - }); - - it('extracts default page size when not a number provided', () => { - expect(extractTrustedAppsListPageLocation({ page_size: 'a' }).page_size).toBe( - MANAGEMENT_DEFAULT_PAGE_SIZE - ); - }); - - it('extracts only last page size when multiple values provided', () => { - expect(extractTrustedAppsListPageLocation({ page_size: ['10', '20'] }).page_size).toBe(20); - }); - - it('extracts proper page size when single valid value provided', () => { - expect(extractTrustedAppsListPageLocation({ page_size: '20' }).page_size).toBe(20); - }); - - it('extracts proper "show" when single valid value provided', () => { - expect(extractTrustedAppsListPageLocation({ show: 'create' }).show).toBe('create'); - }); - - it('extracts only last "show" when multiple values provided', () => { - expect(extractTrustedAppsListPageLocation({ show: ['invalid', 'create'] }).show).toBe( - 'create' - ); - }); - - it('extracts default "show" when no value provided', () => { - expect(extractTrustedAppsListPageLocation({}).show).toBeUndefined(); - }); - - it('extracts default "show" when single invalid value provided', () => { - expect(extractTrustedAppsListPageLocation({ show: 'invalid' }).show).toBeUndefined(); - }); - - it('extracts proper view type when single valid value provided', () => { - expect(extractTrustedAppsListPageLocation({ view_type: 'list' }).view_type).toBe('list'); - }); - - it('extracts only last view type when multiple values provided', () => { - expect(extractTrustedAppsListPageLocation({ view_type: ['grid', 'list'] }).view_type).toBe( - 'list' - ); - }); - - it('extracts default view type when no value provided', () => { - expect(extractTrustedAppsListPageLocation({}).view_type).toBe('grid'); - }); - - it('extracts default view type when single invalid value provided', () => { - expect(extractTrustedAppsListPageLocation({ view_type: 'invalid' }).view_type).toBe('grid'); - }); - }); - - describe('getTrustedAppsListPath()', () => { - it('builds proper path when no parameters provided', () => { - expect(getTrustedAppsListPath()).toEqual('/administration/trusted_apps'); - }); - - it('builds proper path when empty parameters provided', () => { - expect(getTrustedAppsListPath({})).toEqual('/administration/trusted_apps'); - }); - - it('builds proper path when only page size provided', () => { - const pageSize = 20; - expect(getTrustedAppsListPath({ page_size: pageSize })).toEqual( - `/administration/trusted_apps?page_size=${pageSize}` - ); - }); - - it('builds proper path when only page index provided', () => { - const pageIndex = 2; - expect(getTrustedAppsListPath({ page_index: pageIndex })).toEqual( - `/administration/trusted_apps?page_index=${pageIndex}` - ); - }); - - it('builds proper path when only "show" provided', () => { - const show = 'create'; - expect(getTrustedAppsListPath({ show })).toEqual(`/administration/trusted_apps?show=${show}`); - }); - - it('builds proper path when only view type provided', () => { - const viewType = 'list'; - expect(getTrustedAppsListPath({ view_type: viewType })).toEqual( - `/administration/trusted_apps?view_type=${viewType}` - ); - }); - - it('builds proper path when all params provided', () => { - const location: TrustedAppsListPageLocation = { - page_index: 2, - page_size: 20, - show: 'create', - view_type: 'list', - filter: 'test', - included_policies: 'globally', - }; - - expect(getTrustedAppsListPath(location)).toEqual( - `/administration/trusted_apps?page_index=${location.page_index}&page_size=${location.page_size}&view_type=${location.view_type}&show=${location.show}&filter=${location.filter}&included_policies=${location.included_policies}` - ); - }); - - it('builds proper path when page index is equal to default', () => { - const location: TrustedAppsListPageLocation = { - page_index: MANAGEMENT_DEFAULT_PAGE, - page_size: 20, - show: 'create', - view_type: 'list', - filter: '', - included_policies: '', - }; - const path = getTrustedAppsListPath(location); - - expect(path).toEqual( - `/administration/trusted_apps?page_size=${location.page_size}&view_type=${location.view_type}&show=${location.show}` - ); - }); - - it('builds proper path when page size is equal to default', () => { - const location: TrustedAppsListPageLocation = { - page_index: 2, - page_size: MANAGEMENT_DEFAULT_PAGE_SIZE, - show: 'create', - view_type: 'list', - filter: '', - included_policies: '', - }; - const path = getTrustedAppsListPath(location); - - expect(path).toEqual( - `/administration/trusted_apps?page_index=${location.page_index}&view_type=${location.view_type}&show=${location.show}` - ); - }); - - it('builds proper path when "show" is equal to default', () => { - const location: TrustedAppsListPageLocation = { - page_index: 2, - page_size: 20, - show: undefined, - view_type: 'list', - filter: '', - included_policies: '', - }; - const path = getTrustedAppsListPath(location); - - expect(path).toEqual( - `/administration/trusted_apps?page_index=${location.page_index}&page_size=${location.page_size}&view_type=${location.view_type}` - ); - }); - - it('builds proper path when view type is equal to default', () => { - const location: TrustedAppsListPageLocation = { - page_index: 2, - page_size: 20, - show: 'create', - view_type: 'grid', - filter: '', - included_policies: '', - }; - const path = getTrustedAppsListPath(location); - - expect(path).toEqual( - `/administration/trusted_apps?page_index=${location.page_index}&page_size=${location.page_size}&show=${location.show}` - ); - }); - - it('builds proper path when params are equal to default', () => { - const path = getTrustedAppsListPath({ - page_index: MANAGEMENT_DEFAULT_PAGE, - page_size: MANAGEMENT_DEFAULT_PAGE_SIZE, - show: undefined, - view_type: 'grid', - }); - - expect(path).toEqual('/administration/trusted_apps'); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/management/common/routing.ts b/x-pack/plugins/security_solution/public/management/common/routing.ts index 40d17dc766690..828be47d9d0dd 100644 --- a/x-pack/plugins/security_solution/public/management/common/routing.ts +++ b/x-pack/plugins/security_solution/public/management/common/routing.ts @@ -16,7 +16,6 @@ import { paginationFromUrlParams } from '../components/hooks/use_url_pagination' import { EndpointIndexUIQueryParams } from '../pages/endpoint_hosts/types'; import { EventFiltersPageLocation } from '../pages/event_filters/types'; import { PolicyDetailsArtifactsPageLocation } from '../pages/policy/types'; -import { TrustedAppsListPageLocation } from '../pages/trusted_apps/state'; import { AdministrationSubTab } from '../types'; import { MANAGEMENT_DEFAULT_PAGE, @@ -150,30 +149,6 @@ export const getPolicyEventFiltersPath = ( )}`; }; -const normalizeTrustedAppsPageLocation = ( - location?: Partial -): Partial => { - if (location) { - return { - ...(!isDefaultOrMissing(location.page_index, MANAGEMENT_DEFAULT_PAGE) - ? { page_index: location.page_index } - : {}), - ...(!isDefaultOrMissing(location.page_size, MANAGEMENT_DEFAULT_PAGE_SIZE) - ? { page_size: location.page_size } - : {}), - ...(!isDefaultOrMissing(location.view_type, 'grid') ? { view_type: location.view_type } : {}), - ...(!isDefaultOrMissing(location.show, undefined) ? { show: location.show } : {}), - ...(!isDefaultOrMissing(location.id, undefined) ? { id: location.id } : {}), - ...(!isDefaultOrMissing(location.filter, '') ? { filter: location.filter } : ''), - ...(!isDefaultOrMissing(location.included_policies, '') - ? { included_policies: location.included_policies } - : ''), - }; - } else { - return {}; - } -}; - const normalizePolicyDetailsArtifactsListPageLocation = ( location?: Partial ): Partial => { @@ -266,31 +241,12 @@ export const extractArtifactsListPaginationParams = (query: querystring.ParsedUr included_policies: extractIncludedPolicies(query), }); -export const extractTrustedAppsListPageLocation = ( - query: querystring.ParsedUrlQuery -): TrustedAppsListPageLocation => { - const showParamValue = extractFirstParamValue( - query, - 'show' - ) as TrustedAppsListPageLocation['show']; - - return { - ...extractTrustedAppsListPaginationParams(query), - view_type: extractFirstParamValue(query, 'view_type') === 'list' ? 'list' : 'grid', - show: - showParamValue && ['edit', 'create'].includes(showParamValue) ? showParamValue : undefined, - id: extractFirstParamValue(query, 'id'), - }; -}; - -export const getTrustedAppsListPath = (location?: Partial): string => { +export const getTrustedAppsListPath = (location?: Partial): string => { const path = generatePath(MANAGEMENT_ROUTING_TRUSTED_APPS_PATH, { tabName: AdministrationSubTab.trustedApps, }); - return `${path}${appendSearch( - querystring.stringify(normalizeTrustedAppsPageLocation(location)) - )}`; + return getArtifactListPageUrlPath(path, location); }; export const extractPolicyDetailsArtifactsListPageLocation = ( diff --git a/x-pack/plugins/security_solution/public/management/common/url_routing/artifact_list_page_routing.test.ts b/x-pack/plugins/security_solution/public/management/common/url_routing/artifact_list_page_routing.test.ts new file mode 100644 index 0000000000000..efbed1e2e14fc --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/common/url_routing/artifact_list_page_routing.test.ts @@ -0,0 +1,232 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ArtifactListPageUrlParams } from '../../components/artifact_list_page'; +import { MANAGEMENT_DEFAULT_PAGE, MANAGEMENT_DEFAULT_PAGE_SIZE } from '../constants'; +import { + getArtifactListPageUrlPath, + extractArtifactListPageUrlSearchParams, +} from './artifact_list_page_routing'; +import { + getTrustedAppsListPath, + getBlocklistsListPath, + getHostIsolationExceptionsListPath, +} from '../routing'; + +describe('routing', () => { + describe('extractListPaginationParams()', () => { + it('extracts default page when not provided', () => { + expect(extractArtifactListPageUrlSearchParams({}).page).toBe(1); + }); + + it('extracts default page when too small value provided', () => { + expect(extractArtifactListPageUrlSearchParams({ page: '-1' }).page).toBe(1); + }); + + it('extracts default page when not a number provided', () => { + expect(extractArtifactListPageUrlSearchParams({ page: 'a' }).page).toBe(1); + }); + + it('extracts only last page when multiple values provided', () => { + expect(extractArtifactListPageUrlSearchParams({ page: ['1', '2'] }).page).toBe(2); + }); + + it('extracts proper page when single valid value provided', () => { + expect(extractArtifactListPageUrlSearchParams({ page: '2' }).page).toBe(2); + }); + + it('extracts default page size when not provided', () => { + expect(extractArtifactListPageUrlSearchParams({}).pageSize).toBe( + MANAGEMENT_DEFAULT_PAGE_SIZE + ); + }); + + it('extracts default page size when invalid option provided', () => { + expect(extractArtifactListPageUrlSearchParams({ pageSize: '25' }).pageSize).toBe( + MANAGEMENT_DEFAULT_PAGE_SIZE + ); + }); + + it('extracts default page size when not a number provided', () => { + expect(extractArtifactListPageUrlSearchParams({ pageSize: 'a' }).pageSize).toBe( + MANAGEMENT_DEFAULT_PAGE_SIZE + ); + }); + + it('extracts only last page size when multiple values provided', () => { + expect(extractArtifactListPageUrlSearchParams({ pageSize: ['10', '20'] }).pageSize).toBe(20); + }); + + it('extracts proper page size when single valid value provided', () => { + expect(extractArtifactListPageUrlSearchParams({ pageSize: '20' }).pageSize).toBe(20); + }); + + it('extracts proper "show" when single valid value provided', () => { + expect(extractArtifactListPageUrlSearchParams({ show: 'create' }).show).toBe('create'); + }); + + it('extracts only last "show" when multiple values provided', () => { + expect(extractArtifactListPageUrlSearchParams({ show: ['invalid', 'create'] }).show).toBe( + 'create' + ); + }); + + it('extracts default "show" when no value provided', () => { + expect(extractArtifactListPageUrlSearchParams({}).show).toBeUndefined(); + }); + + it('extracts default "show" when single invalid value provided', () => { + expect(extractArtifactListPageUrlSearchParams({ show: 'invalid' }).show).toBeUndefined(); + }); + }); + + describe('getArtifactListPageUrlPath()', () => { + it.each([ + ['trusted_apps', getTrustedAppsListPath], + ['blocklist', getBlocklistsListPath], + ['host_isolation_exceptions', getHostIsolationExceptionsListPath], + ])('builds proper path for %s when no parameters provided', (path, getPath) => { + expect(getArtifactListPageUrlPath(getPath())).toEqual(`/administration/${path}`); + }); + + it.each([ + ['trusted_apps', getTrustedAppsListPath], + ['blocklist', getBlocklistsListPath], + ['host_isolation_exceptions', getHostIsolationExceptionsListPath], + ])('builds proper path for %s when only page size provided', (path, getPath) => { + const pageSize = 20; + expect(getArtifactListPageUrlPath(getPath({ pageSize }))).toEqual( + `/administration/${path}?pageSize=${pageSize}` + ); + }); + + it.each([ + ['trusted_apps', getTrustedAppsListPath], + ['blocklist', getBlocklistsListPath], + ['host_isolation_exceptions', getHostIsolationExceptionsListPath], + ])('builds proper path for %s when only page index provided', (path, getPath) => { + const pageIndex = 2; + expect(getArtifactListPageUrlPath(getPath({ page: pageIndex }))).toEqual( + `/administration/${path}?page=${pageIndex}` + ); + }); + + it.each([ + ['trusted_apps', getTrustedAppsListPath], + ['blocklist', getBlocklistsListPath], + ['host_isolation_exceptions', getHostIsolationExceptionsListPath], + ])('builds proper path for %s when only "show" provided', (path, getPath) => { + const show = 'create'; + expect(getArtifactListPageUrlPath(getPath({ show }))).toEqual( + `/administration/${path}?show=${show}` + ); + }); + + it.each([ + ['trusted_apps', getTrustedAppsListPath], + ['blocklist', getBlocklistsListPath], + ['host_isolation_exceptions', getHostIsolationExceptionsListPath], + ])('builds proper path for %s when all params provided', (path, getPath) => { + const location: ArtifactListPageUrlParams = { + page: 2, + pageSize: 20, + show: 'create', + filter: 'test', + includedPolicies: 'globally', + }; + + expect(getPath(location)).toEqual( + `/administration/${path}?page=${location.page}&pageSize=${location.pageSize}&show=${location.show}&filter=${location.filter}&includedPolicies=${location.includedPolicies}` + ); + }); + + it.each([ + ['trusted_apps', getTrustedAppsListPath], + ['blocklist', getBlocklistsListPath], + ['host_isolation_exceptions', getHostIsolationExceptionsListPath], + ])('builds proper path for %s when page index is equal to default', (path, getPath) => { + const location: ArtifactListPageUrlParams = { + page: MANAGEMENT_DEFAULT_PAGE, + pageSize: 20, + show: 'create', + filter: '', + includedPolicies: '', + }; + + expect(getPath(location)).toEqual( + `/administration/${path}?pageSize=${location.pageSize}&show=${location.show}` + ); + }); + + it.each([ + ['trusted_apps', getTrustedAppsListPath], + ['blocklist', getBlocklistsListPath], + ['host_isolation_exceptions', getHostIsolationExceptionsListPath], + ])('builds proper path for %s when page size is equal to default', (path, getPath) => { + const location: ArtifactListPageUrlParams = { + page: 2, + pageSize: MANAGEMENT_DEFAULT_PAGE_SIZE, + show: 'create', + filter: '', + includedPolicies: '', + }; + + expect(getPath(location)).toEqual( + `/administration/${path}?page=${location.page}&show=${location.show}` + ); + }); + + it.each([ + ['trusted_apps', getTrustedAppsListPath], + ['blocklist', getBlocklistsListPath], + ['host_isolation_exceptions', getHostIsolationExceptionsListPath], + ])('builds proper path for %s when "show" is equal to default', (path, getPath) => { + const location: ArtifactListPageUrlParams = { + page: 2, + pageSize: 20, + show: undefined, + filter: '', + includedPolicies: '', + }; + + expect(getPath(location)).toEqual( + `/administration/${path}?page=${location.page}&pageSize=${location.pageSize}` + ); + }); + + it.each([ + ['trusted_apps', getTrustedAppsListPath], + ['blocklist', getBlocklistsListPath], + ['host_isolation_exceptions', getHostIsolationExceptionsListPath], + ])('builds proper path for %s when view type is equal to default', (path, getPath) => { + const location: ArtifactListPageUrlParams = { + page: 2, + pageSize: 20, + show: 'create', + filter: '', + includedPolicies: '', + }; + + expect(getPath(location)).toEqual( + `/administration/${path}?page=${location.page}&pageSize=${location.pageSize}&show=${location.show}` + ); + }); + + it.each([ + ['trusted_apps', getTrustedAppsListPath], + ['blocklist', getBlocklistsListPath], + ['host_isolation_exceptions', getHostIsolationExceptionsListPath], + ])('builds proper path for %s when params are equal to default', (path, getPath) => { + const location: ArtifactListPageUrlParams = { + page: MANAGEMENT_DEFAULT_PAGE, + pageSize: MANAGEMENT_DEFAULT_PAGE_SIZE, + }; + + expect(getPath(location)).toEqual(`/administration/${path}`); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/common/url_routing/utils.ts b/x-pack/plugins/security_solution/public/management/common/url_routing/utils.ts index b9ec8b2fb7a82..afff61e94b06d 100644 --- a/x-pack/plugins/security_solution/public/management/common/url_routing/utils.ts +++ b/x-pack/plugins/security_solution/public/management/common/url_routing/utils.ts @@ -14,8 +14,8 @@ import { MANAGEMENT_DEFAULT_PAGE_SIZE, MANAGEMENT_PAGE_SIZE_OPTIONS } from '../c * @param value * @param defaultValue */ -export const isDefaultOrMissing = (value: T | undefined, defaultValue: T) => { - return value === undefined || value === defaultValue; +export const isDefaultOrMissing = (value: T | undefined | 0, defaultValue: T) => { + return value === undefined || value === defaultValue || value === 0; }; /** diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_flyout.test.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_flyout.test.tsx index 81dc91dbae32c..b21579d25dedb 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_flyout.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_flyout.test.tsx @@ -16,6 +16,8 @@ import { getEndpointPrivilegesInitialStateMock } from '../../../../common/compon import { AppContextTestRender } from '../../../../common/mock/endpoint'; import { trustedAppsAllHttpMocks } from '../../../pages/mocks'; import { useUserPrivileges as _useUserPrivileges } from '../../../../common/components/user_privileges'; +import { entriesToConditionEntries } from '../../../../common/utils/exception_list_items/mappers'; +import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; jest.mock('../../../../common/components/user_privileges'); const useUserPrivileges = _useUserPrivileges as jest.Mock; @@ -357,12 +359,19 @@ describe('When the flyout is opened in the ArtifactListPage component', () => { }); }); - expect(getLastFormComponentProps().item).toEqual({ + const expectedProps = { ...mockedApi.responseProvider.trustedApp({ query: { item_id: '123' }, } as unknown as HttpFetchOptionsWithPath), created_at: expect.any(String), - }); + }; + + // map process.hash entries to have * as suffix + expectedProps.entries = entriesToConditionEntries( + expectedProps.entries + ) as ExceptionListItemSchema['entries']; + + expect(getLastFormComponentProps().item).toEqual(expectedProps); }); it('should show error toast and close flyout if item for edit does not exist', async () => { diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/mocks.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/mocks.tsx index d4f92bd2717f2..ebf98c64d7dc4 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/mocks.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/mocks.tsx @@ -14,7 +14,7 @@ import { ArtifactFormComponentProps } from './types'; import { ArtifactListPage, ArtifactListPageProps } from './artifact_list_page'; import { AppContextTestRender, createAppRootMockRenderer } from '../../../common/mock/endpoint'; import { trustedAppsAllHttpMocks } from '../../pages/mocks'; -import { TrustedAppsApiClient } from '../../pages/trusted_apps/service/trusted_apps_api_client'; +import { TrustedAppsApiClient } from '../../pages/trusted_apps/service/api_client'; import { artifactListPageLabels } from './translations'; export const getFormComponentMock = (): { diff --git a/x-pack/plugins/security_solution/public/management/pages/blocklist/view/components/blocklist_form.tsx b/x-pack/plugins/security_solution/public/management/pages/blocklist/view/components/blocklist_form.tsx index 3926d42fb36cf..ccb2ec9055fc8 100644 --- a/x-pack/plugins/security_solution/public/management/pages/blocklist/view/components/blocklist_form.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/blocklist/view/components/blocklist_form.tsx @@ -61,7 +61,7 @@ import { BY_POLICY_ARTIFACT_TAG_PREFIX, } from '../../../../../../common/endpoint/service/artifacts/constants'; import { useLicense } from '../../../../../common/hooks/use_license'; -import { isValidHash } from '../../../../../../common/endpoint/service/trusted_apps/validations'; +import { isValidHash } from '../../../../../../common/endpoint/service/artifacts/validations'; import { isArtifactGlobal } from '../../../../../../common/endpoint/service/artifacts'; import type { PolicyData } from '../../../../../../common/endpoint/types'; import { isGlobalPolicyEffected } from '../../../../components/effected_policy_select/utils'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/test_utils/mocks.ts b/x-pack/plugins/security_solution/public/management/pages/policy/test_utils/mocks.ts index c1b30b6fad10c..e3641ba66e15b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/test_utils/mocks.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/test_utils/mocks.ts @@ -5,8 +5,61 @@ * 2.0. */ -import { GetTrustedAppsListResponse } from '../../../../../common/endpoint/types'; -import { createSampleTrustedApps } from '../../trusted_apps/test_utils'; +import { OperatingSystem } from '@kbn/securitysolution-utils'; +import { GetTrustedAppsListResponse, TrustedApp } from '../../../../../common/endpoint/types'; +import { + MANAGEMENT_DEFAULT_PAGE, + MANAGEMENT_DEFAULT_PAGE_SIZE, + MANAGEMENT_PAGE_SIZE_OPTIONS, +} from '../../../common/constants'; + +interface Pagination { + pageIndex: number; + pageSize: number; + totalItemCount: number; + pageSizeOptions: number[]; +} + +const OPERATING_SYSTEMS: OperatingSystem[] = [ + OperatingSystem.WINDOWS, + OperatingSystem.MAC, + OperatingSystem.LINUX, +]; + +const generate = (count: number, generator: (i: number) => T) => + [...new Array(count).keys()].map(generator); + +const createSampleTrustedApp = (i: number, longTexts?: boolean): TrustedApp => { + return { + id: String(i), + version: 'abc123', + name: generate(longTexts ? 10 : 1, () => `trusted app ${i}`).join(' '), + description: generate(longTexts ? 10 : 1, () => `Trusted App ${i}`).join(' '), + created_at: '1 minute ago', + created_by: 'someone', + updated_at: '1 minute ago', + updated_by: 'someone', + os: OPERATING_SYSTEMS[i % 3], + entries: [], + effectScope: { type: 'global' }, + }; +}; + +const createDefaultPagination = (): Pagination => ({ + pageIndex: MANAGEMENT_DEFAULT_PAGE, + pageSize: MANAGEMENT_DEFAULT_PAGE_SIZE, + totalItemCount: 200, + pageSizeOptions: [...MANAGEMENT_PAGE_SIZE_OPTIONS], +}); + +const createSampleTrustedApps = ( + pagination: Partial, + longTexts?: boolean +): TrustedApp[] => { + const fullPagination = { ...createDefaultPagination(), ...pagination }; + + return generate(fullPagination.pageSize, (i: number) => createSampleTrustedApp(i, longTexts)); +}; export const getMockListResponse: () => GetTrustedAppsListResponse = () => ({ data: createSampleTrustedApps({}), diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/policy_artifacts_empty_unassigned.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/policy_artifacts_empty_unassigned.tsx index 4809ee53b0d7b..2bee159dc6e4d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/policy_artifacts_empty_unassigned.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/policy_artifacts_empty_unassigned.tsx @@ -12,7 +12,6 @@ import { useGetLinkTo } from './use_policy_artifacts_empty_hooks'; import { useUserPrivileges } from '../../../../../../common/components/user_privileges'; import { POLICY_ARTIFACT_EMPTY_UNASSIGNED_LABELS } from './translations'; import { EventFiltersPageLocation } from '../../../../event_filters/types'; -import { TrustedAppsListPageLocation } from '../../../../trusted_apps/state'; import { ArtifactListPageUrlParams } from '../../../../../components/artifact_list_page'; interface CommonProps { policyId: string; @@ -21,10 +20,7 @@ interface CommonProps { labels: typeof POLICY_ARTIFACT_EMPTY_UNASSIGNED_LABELS; getPolicyArtifactsPath: (policyId: string) => string; getArtifactPath: ( - location?: - | Partial - | Partial - | Partial + location?: Partial | Partial ) => string; } diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/policy_artifacts_empty_unexisting.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/policy_artifacts_empty_unexisting.tsx index eb0133823ea99..7d12389048753 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/policy_artifacts_empty_unexisting.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/policy_artifacts_empty_unexisting.tsx @@ -10,7 +10,6 @@ import { EuiEmptyPrompt, EuiButton, EuiPageTemplate } from '@elastic/eui'; import { useGetLinkTo } from './use_policy_artifacts_empty_hooks'; import { POLICY_ARTIFACT_EMPTY_UNEXISTING_LABELS } from './translations'; import { EventFiltersPageLocation } from '../../../../event_filters/types'; -import { TrustedAppsListPageLocation } from '../../../../trusted_apps/state'; import { ArtifactListPageUrlParams } from '../../../../../components/artifact_list_page'; interface CommonProps { @@ -19,10 +18,7 @@ interface CommonProps { labels: typeof POLICY_ARTIFACT_EMPTY_UNEXISTING_LABELS; getPolicyArtifactsPath: (policyId: string) => string; getArtifactPath: ( - location?: - | Partial - | Partial - | Partial + location?: Partial | Partial ) => string; } diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/use_policy_artifacts_empty_hooks.ts b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/use_policy_artifacts_empty_hooks.ts index 723988a9de468..b2ee8e7dd17ba 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/use_policy_artifacts_empty_hooks.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/use_policy_artifacts_empty_hooks.ts @@ -11,7 +11,6 @@ import { useNavigateToAppEventHandler } from '../../../../../../common/hooks/end import { useAppUrl } from '../../../../../../common/lib/kibana/hooks'; import { APP_UI_ID } from '../../../../../../../common/constants'; import { EventFiltersPageLocation } from '../../../../event_filters/types'; -import { TrustedAppsListPageLocation } from '../../../../trusted_apps/state'; import { ArtifactListPageUrlParams } from '../../../../../components/artifact_list_page'; export const useGetLinkTo = ( @@ -19,10 +18,7 @@ export const useGetLinkTo = ( policyName: string, getPolicyArtifactsPath: (policyId: string) => string, getArtifactPath: ( - location?: - | Partial - | Partial - | Partial + location?: Partial | Partial ) => string, location?: Partial<{ show: 'create' }> ) => { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/layout/policy_artifacts_layout.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/layout/policy_artifacts_layout.tsx index 92b0edc690a44..ac3cf9e536b9a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/layout/policy_artifacts_layout.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/layout/policy_artifacts_layout.tsx @@ -32,7 +32,6 @@ import { PolicyArtifactsFlyout } from '../flyout'; import { PolicyArtifactsPageLabels, policyArtifactsPageLabels } from '../translations'; import { PolicyArtifactsDeleteModal } from '../delete_modal'; import { EventFiltersPageLocation } from '../../../../event_filters/types'; -import { TrustedAppsListPageLocation } from '../../../../trusted_apps/state'; import { ArtifactListPageUrlParams } from '../../../../../components/artifact_list_page'; interface PolicyArtifactsLayoutProps { @@ -42,10 +41,7 @@ interface PolicyArtifactsLayoutProps { getExceptionsListApiClient: () => ExceptionsListApiClient; searchableFields: readonly string[]; getArtifactPath: ( - location?: - | Partial - | Partial - | Partial + location?: Partial | Partial ) => string; getPolicyArtifactsPath: (policyId: string) => string; /** A boolean to check extra privileges for restricted actions, true when it's allowed, false when not */ diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/policy_artifacts_list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/policy_artifacts_list.tsx index 863b7de4a4815..445725915899b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/policy_artifacts_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/policy_artifacts_list.tsx @@ -29,7 +29,6 @@ import { ExceptionsListApiClient } from '../../../../../services/exceptions_list import { useListArtifact } from '../../../../../hooks/artifacts'; import { POLICY_ARTIFACT_LIST_LABELS } from './translations'; import { EventFiltersPageLocation } from '../../../../event_filters/types'; -import { TrustedAppsListPageLocation } from '../../../../trusted_apps/state'; import { ArtifactListPageUrlParams } from '../../../../../components/artifact_list_page'; interface PolicyArtifactsListProps { @@ -37,10 +36,7 @@ interface PolicyArtifactsListProps { apiClient: ExceptionsListApiClient; searchableFields: string[]; getArtifactPath: ( - location?: - | Partial - | Partial - | Partial + location?: Partial | Partial ) => string; getPolicyArtifactsPath: (policyId: string) => string; labels: typeof POLICY_ARTIFACT_LIST_LABELS; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/endpoint_package_custom_extension.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/endpoint_package_custom_extension.tsx index e6984087bef5a..7700c1f71fbab 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/endpoint_package_custom_extension.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/endpoint_package_custom_extension.tsx @@ -10,7 +10,7 @@ import { EuiSpacer } from '@elastic/eui'; import { PackageCustomExtensionComponentProps } from '@kbn/fleet-plugin/public'; import { useHttp } from '../../../../../../common/lib/kibana'; import { useCanSeeHostIsolationExceptionsMenu } from '../../../../host_isolation_exceptions/view/hooks'; -import { TrustedAppsApiClient } from '../../../../trusted_apps/service/trusted_apps_api_client'; +import { TrustedAppsApiClient } from '../../../../trusted_apps/service/api_client'; import { EventFiltersApiClient } from '../../../../event_filters/service/event_filters_api_client'; import { HostIsolationExceptionsApiClient } from '../../../../host_isolation_exceptions/host_isolation_exceptions_api_client'; import { BlocklistsApiClient } from '../../../../blocklist/services'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension.tsx index 87537014e6b0f..dfb2677ecb594 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension.tsx @@ -36,7 +36,7 @@ import { FleetIntegrationArtifactsCard } from './endpoint_package_custom_extensi import { BlocklistsApiClient } from '../../../blocklist/services'; import { HostIsolationExceptionsApiClient } from '../../../host_isolation_exceptions/host_isolation_exceptions_api_client'; import { EventFiltersApiClient } from '../../../event_filters/service/event_filters_api_client'; -import { TrustedAppsApiClient } from '../../../trusted_apps/service/trusted_apps_api_client'; +import { TrustedAppsApiClient } from '../../../trusted_apps/service/api_client'; import { SEARCHABLE_FIELDS as BLOCKLIST_SEARCHABLE_FIELDS } from '../../../blocklist/constants'; import { SEARCHABLE_FIELDS as HOST_ISOLATION_EXCEPTIONS_SEARCHABLE_FIELDS } from '../../../host_isolation_exceptions/constants'; import { SEARCHABLE_FIELDS as EVENT_FILTERS_SEARCHABLE_FIELDS } from '../../../event_filters/constants'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/tabs/policy_tabs.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/tabs/policy_tabs.tsx index 62778bb164d98..f3a20a1abfd66 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/tabs/policy_tabs.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/tabs/policy_tabs.tsx @@ -41,7 +41,7 @@ import { POLICY_ARTIFACT_EVENT_FILTERS_LABELS } from './event_filters_translatio import { POLICY_ARTIFACT_TRUSTED_APPS_LABELS } from './trusted_apps_translations'; import { POLICY_ARTIFACT_HOST_ISOLATION_EXCEPTIONS_LABELS } from './host_isolation_exceptions_translations'; import { POLICY_ARTIFACT_BLOCKLISTS_LABELS } from './blocklists_translations'; -import { TrustedAppsApiClient } from '../../../trusted_apps/service/trusted_apps_api_client'; +import { TrustedAppsApiClient } from '../../../trusted_apps/service/api_client'; import { EventFiltersApiClient } from '../../../event_filters/service/event_filters_api_client'; import { BlocklistsApiClient } from '../../../blocklist/services/blocklists_api_client'; import { HostIsolationExceptionsApiClient } from '../../../host_isolation_exceptions/host_isolation_exceptions_api_client'; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/index.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/index.tsx index 018987c81b94d..d9edd9986e156 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/index.tsx @@ -6,16 +6,18 @@ */ import { Switch, Route } from 'react-router-dom'; -import React from 'react'; -import { TrustedAppsPage } from './view'; +import React, { memo } from 'react'; +import { TrustedAppsList } from './view/trusted_apps_list'; import { MANAGEMENT_ROUTING_TRUSTED_APPS_PATH } from '../../common/constants'; import { NotFoundPage } from '../../../app/404'; -export function TrustedAppsContainer() { +export const TrustedAppsContainer = memo(() => { return ( - + ); -} +}); + +TrustedAppsContainer.displayName = 'TrustedAppsContainer'; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/api_client.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/api_client.ts new file mode 100644 index 0000000000000..e66c16717e428 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/api_client.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + CreateExceptionListItemSchema, + ExceptionListItemSchema, + UpdateExceptionListItemSchema, +} from '@kbn/securitysolution-io-ts-list-types'; +import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '@kbn/securitysolution-list-constants'; + +import { HttpStart } from '@kbn/core/public'; +import { ConditionEntry } from '../../../../../common/endpoint/types'; +import { + conditionEntriesToEntries, + entriesToConditionEntries, +} from '../../../../common/utils/exception_list_items'; +import { ExceptionsListApiClient } from '../../../services/exceptions_list/exceptions_list_api_client'; +import { TRUSTED_APPS_EXCEPTION_LIST_DEFINITION } from '../constants'; + +function readTransform(item: ExceptionListItemSchema): ExceptionListItemSchema { + return { + ...item, + entries: entriesToConditionEntries(item.entries) as ExceptionListItemSchema['entries'], + }; +} + +function writeTransform( + item: T +): T { + return { + ...item, + entries: conditionEntriesToEntries(item.entries as ConditionEntry[], true), + } as T; +} + +/** + * Trusted Apps exceptions Api client class using ExceptionsListApiClient as base class + * It follows the Singleton pattern. + * Please, use the getInstance method instead of creating a new instance when using this implementation. + */ +export class TrustedAppsApiClient extends ExceptionsListApiClient { + constructor(http: HttpStart) { + super( + http, + ENDPOINT_TRUSTED_APPS_LIST_ID, + TRUSTED_APPS_EXCEPTION_LIST_DEFINITION, + readTransform, + writeTransform + ); + } + + public static getInstance(http: HttpStart): ExceptionsListApiClient { + return super.getInstance( + http, + ENDPOINT_TRUSTED_APPS_LIST_ID, + TRUSTED_APPS_EXCEPTION_LIST_DEFINITION, + readTransform, + writeTransform + ); + } +} diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/index.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/index.ts index 3c0d58043dd02..4b2dee13ce70a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/index.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export * from './trusted_apps_http_service'; +export { TrustedAppsApiClient } from './api_client'; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/trusted_apps_api_client.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/trusted_apps_api_client.ts deleted file mode 100644 index 574f32dca6b33..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/trusted_apps_api_client.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '@kbn/securitysolution-list-constants'; -import { HttpStart } from '@kbn/core/public'; -import { ExceptionsListApiClient } from '../../../services/exceptions_list/exceptions_list_api_client'; -import { TRUSTED_APPS_EXCEPTION_LIST_DEFINITION } from '../constants'; - -/** - * Trusted apps Api client class using ExceptionsListApiClient as base class - * It follow the Singleton pattern. - * Please, use the getInstance method instead of creating a new instance when using this implementation. - */ -export class TrustedAppsApiClient extends ExceptionsListApiClient { - constructor(http: HttpStart) { - super(http, ENDPOINT_TRUSTED_APPS_LIST_ID, TRUSTED_APPS_EXCEPTION_LIST_DEFINITION); - } - - public static getInstance(http: HttpStart): ExceptionsListApiClient { - return super.getInstance( - http, - ENDPOINT_TRUSTED_APPS_LIST_ID, - TRUSTED_APPS_EXCEPTION_LIST_DEFINITION - ); - } -} diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/trusted_apps_http_service.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/trusted_apps_http_service.ts deleted file mode 100644 index a16ccd9169dbe..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/trusted_apps_http_service.ts +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - ExceptionListItemSchema, - ExceptionListSchema, - ExceptionListSummarySchema, - FoundExceptionListItemSchema, -} from '@kbn/securitysolution-io-ts-list-types'; -import { - ENDPOINT_TRUSTED_APPS_LIST_ID, - EXCEPTION_LIST_ITEM_URL, - EXCEPTION_LIST_URL, -} from '@kbn/securitysolution-list-constants'; -import { HttpStart } from '@kbn/core/public'; -import pMap from 'p-map'; -import { toUpdateTrustedApp } from '../../../../../common/endpoint/service/trusted_apps/to_update_trusted_app'; -import { - DeleteTrustedAppsRequestParams, - GetOneTrustedAppRequestParams, - GetOneTrustedAppResponse, - GetTrustedAppsListRequest, - GetTrustedAppsListResponse, - MaybeImmutable, - PostTrustedAppCreateRequest, - PostTrustedAppCreateResponse, - PutTrustedAppsRequestParams, - PutTrustedAppUpdateRequest, - PutTrustedAppUpdateResponse, - TrustedApp, -} from '../../../../../common/endpoint/types'; -import { sendGetEndpointSpecificPackagePolicies } from '../../../services/policies/policies'; -import { TRUSTED_APPS_EXCEPTION_LIST_DEFINITION } from '../constants'; -import { isGlobalEffectScope } from '../state/type_guards'; -import { - exceptionListItemToTrustedApp, - newTrustedAppToCreateExceptionListItem, - updatedTrustedAppToUpdateExceptionListItem, -} from './mappers'; -import { validateTrustedAppHttpRequestBody } from './validate_trusted_app_http_request_body'; - -export interface TrustedAppsService { - getTrustedApp(params: GetOneTrustedAppRequestParams): Promise; - - getTrustedAppsList(request: GetTrustedAppsListRequest): Promise; - - deleteTrustedApp(request: DeleteTrustedAppsRequestParams): Promise; - - createTrustedApp(request: PostTrustedAppCreateRequest): Promise; - - updateTrustedApp( - params: PutTrustedAppsRequestParams, - request: PutTrustedAppUpdateRequest - ): Promise; - - getPolicyList( - options?: Parameters[1] - ): ReturnType; - - assignPolicyToTrustedApps( - policyId: string, - trustedApps: MaybeImmutable - ): Promise; - - removePolicyFromTrustedApps( - policyId: string, - trustedApps: MaybeImmutable - ): Promise; -} - -const P_MAP_OPTIONS = Object.freeze({ - concurrency: 5, - /** When set to false, instead of stopping when a promise rejects, it will wait for all the promises to settle - * and then reject with an aggregated error containing all the errors from the rejected promises. */ - stopOnError: false, -}); - -export class TrustedAppsHttpService implements TrustedAppsService { - private readonly getHttpService: () => Promise; - - constructor(http: HttpStart) { - let ensureListExists: Promise; - - this.getHttpService = async () => { - if (!ensureListExists) { - ensureListExists = http - .post(EXCEPTION_LIST_URL, { - body: JSON.stringify(TRUSTED_APPS_EXCEPTION_LIST_DEFINITION), - }) - .then(() => {}) - .catch((err) => { - if (err.response.status !== 409) { - return Promise.reject(err); - } - }); - } - - await ensureListExists; - return http; - }; - } - - private async getExceptionListItem(itemId: string): Promise { - return (await this.getHttpService()).get(EXCEPTION_LIST_ITEM_URL, { - query: { - item_id: itemId, - namespace_type: 'agnostic', - }, - }); - } - - async getTrustedApp(params: GetOneTrustedAppRequestParams) { - const exceptionItem = await this.getExceptionListItem(params.id); - - return { - data: exceptionListItemToTrustedApp(exceptionItem), - }; - } - - async getTrustedAppsList({ - page = 1, - per_page: perPage = 10, - kuery, - }: GetTrustedAppsListRequest): Promise { - const itemListResults = await ( - await this.getHttpService() - ).get(`${EXCEPTION_LIST_ITEM_URL}/_find`, { - query: { - page, - per_page: perPage, - filter: kuery, - sort_field: 'name', - sort_order: 'asc', - list_id: [ENDPOINT_TRUSTED_APPS_LIST_ID], - namespace_type: ['agnostic'], - }, - }); - - return { - ...itemListResults, - data: itemListResults.data.map(exceptionListItemToTrustedApp), - }; - } - - async deleteTrustedApp(request: DeleteTrustedAppsRequestParams): Promise { - await ( - await this.getHttpService() - ).delete(EXCEPTION_LIST_ITEM_URL, { - query: { - item_id: request.id, - namespace_type: 'agnostic', - }, - }); - } - - async createTrustedApp(request: PostTrustedAppCreateRequest) { - await validateTrustedAppHttpRequestBody(await this.getHttpService(), request); - - const newTrustedAppException = newTrustedAppToCreateExceptionListItem(request); - const createdExceptionItem = await ( - await this.getHttpService() - ).post(EXCEPTION_LIST_ITEM_URL, { - body: JSON.stringify(newTrustedAppException), - }); - - return { - data: exceptionListItemToTrustedApp(createdExceptionItem), - }; - } - - async updateTrustedApp( - params: PutTrustedAppsRequestParams, - updatedTrustedApp: PutTrustedAppUpdateRequest - ) { - const [currentExceptionListItem] = await Promise.all([ - await this.getExceptionListItem(params.id), - await validateTrustedAppHttpRequestBody(await this.getHttpService(), updatedTrustedApp), - ]); - - const updatedExceptionListItem = await ( - await this.getHttpService() - ).put(EXCEPTION_LIST_ITEM_URL, { - body: JSON.stringify( - updatedTrustedAppToUpdateExceptionListItem(currentExceptionListItem, updatedTrustedApp) - ), - }); - - return { - data: exceptionListItemToTrustedApp(updatedExceptionListItem), - }; - } - - async getTrustedAppsSummary(filter?: string) { - return (await this.getHttpService()).get( - `${EXCEPTION_LIST_URL}/summary`, - { - query: { - filter, - list_id: ENDPOINT_TRUSTED_APPS_LIST_ID, - namespace_type: 'agnostic', - }, - } - ); - } - - async getPolicyList(options?: Parameters[1]) { - return sendGetEndpointSpecificPackagePolicies(await this.getHttpService(), options); - } - - /** - * Assign a policy to trusted apps. Note that Trusted Apps MUST NOT be global - * - * @param policyId - * @param trustedApps[] - */ - assignPolicyToTrustedApps( - policyId: string, - trustedApps: MaybeImmutable - ): Promise { - return this._handleAssignOrRemovePolicyId('assign', policyId, trustedApps); - } - - /** - * Remove a policy from trusted apps. Note that Trusted Apps MUST NOT be global - * - * @param policyId - * @param trustedApps[] - */ - removePolicyFromTrustedApps( - policyId: string, - trustedApps: MaybeImmutable - ): Promise { - return this._handleAssignOrRemovePolicyId('remove', policyId, trustedApps); - } - - private _handleAssignOrRemovePolicyId( - action: 'assign' | 'remove', - policyId: string, - trustedApps: MaybeImmutable - ): Promise { - if (policyId.trim() === '') { - throw new Error('policy ID is required'); - } - - if (trustedApps.length === 0) { - throw new Error('at least one trusted app is required'); - } - - return pMap( - trustedApps, - async (trustedApp) => { - if (isGlobalEffectScope(trustedApp.effectScope)) { - throw new Error( - `Unable to update trusted app [${trustedApp.id}] policy assignment. It's effectScope is 'global'` - ); - } - - const policies: string[] = !isGlobalEffectScope(trustedApp.effectScope) - ? [...trustedApp.effectScope.policies] - : []; - - const indexOfPolicyId = policies.indexOf(policyId); - - if (action === 'assign' && indexOfPolicyId === -1) { - policies.push(policyId); - } else if (action === 'remove' && indexOfPolicyId !== -1) { - policies.splice(indexOfPolicyId, 1); - } - - return this.updateTrustedApp( - { id: trustedApp.id }, - { - ...toUpdateTrustedApp(trustedApp), - effectScope: { - type: 'policy', - policies, - }, - } - ); - }, - P_MAP_OPTIONS - ); - } -} diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/validate_trusted_app_http_request_body.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/validate_trusted_app_http_request_body.ts deleted file mode 100644 index 69b281e88fc9b..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/validate_trusted_app_http_request_body.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { HttpStart } from '@kbn/core/public'; -import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; -import { - PostTrustedAppCreateRequest, - PutTrustedAppUpdateRequest, -} from '../../../../../common/endpoint/types'; -import { HttpRequestValidationError } from './errors'; -import { sendGetAgentPolicyList } from '../../../services/policies/ingest'; - -/** - * Validates that the Trusted App is valid for sending to the API (`POST` + 'PUT') - * - * @throws - */ -export const validateTrustedAppHttpRequestBody = async ( - http: HttpStart, - trustedApp: PostTrustedAppCreateRequest | PutTrustedAppUpdateRequest -): Promise => { - const failedValidations: string[] = []; - - // Validate that the Policy IDs are still valid - if (trustedApp.effectScope.type === 'policy' && trustedApp.effectScope.policies.length) { - const policyIds = trustedApp.effectScope.policies; - - // We can't search against the Package Policy API by ID because there is no way to do that. - // So, as a work-around, we use the Agent Policy API and check for those Agent Policies that - // have these package policies in it. For endpoint, these are 1-to-1. - const agentPoliciesFound = await sendGetAgentPolicyList(http, { - query: { - kuery: `${AGENT_POLICY_SAVED_OBJECT_TYPE}.package_policies: (${policyIds.join(' or ')})`, - }, - }); - - if (!agentPoliciesFound.items.length) { - failedValidations.push(`Invalid Policy Id(s) [${policyIds.join(', ')}]`); - } else { - const missingPolicies = policyIds.filter( - (policyId) => - !agentPoliciesFound.items.find(({ package_policies: packagePolicies }) => - (packagePolicies as string[]).includes(policyId) - ) - ); - - if (missingPolicies.length) { - failedValidations.push(`Invalid Policy Id(s) [${missingPolicies.join(', ')}]`); - } - } - } - - if (failedValidations.length) { - throw new HttpRequestValidationError(failedValidations); - } -}; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/index.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/index.ts deleted file mode 100644 index a06fceab29d4c..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export * from '../../../state/async_resource_state'; -export * from './trusted_apps_list_page_state'; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts deleted file mode 100644 index 292b982eebb47..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { NewTrustedApp, TrustedApp } from '../../../../../common/endpoint/types/trusted_apps'; -import { AsyncResourceState } from '../../../state/async_resource_state'; -import { GetPolicyListResponse } from '../../policy/types'; - -export interface Pagination { - pageIndex: number; - pageSize: number; - totalItemCount: number; - pageSizeOptions: number[]; -} - -export interface TrustedAppsListData { - items: TrustedApp[]; - pageIndex: number; - pageSize: number; - timestamp: number; - totalItemsCount: number; - filter: string; - includedPolicies: string; -} - -export type ViewType = 'list' | 'grid'; - -export interface TrustedAppsListPageLocation { - page_index: number; - page_size: number; - view_type: ViewType; - show?: 'create' | 'edit'; - /** Used for editing. The ID of the selected trusted app */ - id?: string; - filter: string; - // A string with comma dlimetered list of included policy IDs - included_policies: string; -} - -export interface TrustedAppsListPageState { - /** Represents if trusted apps entries exist, regardless of whether the list is showing results - * or not (which could use filtering in the future) - */ - entriesExist: AsyncResourceState; - listView: { - listResourceState: AsyncResourceState; - freshDataTimestamp: number; - }; - deletionDialog: { - entry?: TrustedApp; - confirmed: boolean; - submissionResourceState: AsyncResourceState; - }; - creationDialog: { - formState?: { - entry: NewTrustedApp; - isValid: boolean; - }; - /** The trusted app to be edited (when in edit mode) */ - editItem?: AsyncResourceState; - confirmed: boolean; - submissionResourceState: AsyncResourceState; - }; - /** A list of all available polices for use in associating TA to policies */ - policies: AsyncResourceState; - location: TrustedAppsListPageLocation; - active: boolean; - forceRefresh: boolean; -} diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/type_guards.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/type_guards.ts index 1a28e6f3bfecf..239255b641bf4 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/type_guards.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/type_guards.ts @@ -8,11 +8,7 @@ import { ConditionEntryField } from '@kbn/securitysolution-utils'; import { TrustedAppConditionEntry, - EffectScope, - GlobalEffectScope, MacosLinuxConditionEntry, - MaybeImmutable, - PolicyEffectScope, WindowsConditionEntry, } from '../../../../../common/endpoint/types'; @@ -27,15 +23,3 @@ export const isMacosLinuxTrustedAppCondition = ( ): condition is MacosLinuxConditionEntry => { return condition.field !== ConditionEntryField.SIGNER; }; - -export const isGlobalEffectScope = ( - effectedScope: MaybeImmutable -): effectedScope is GlobalEffectScope => { - return effectedScope.type === 'global'; -}; - -export const isPolicyEffectScope = ( - effectedScope: MaybeImmutable -): effectedScope is PolicyEffectScope => { - return effectedScope.type === 'policy'; -}; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/action.ts deleted file mode 100644 index f9965da4a2256..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/action.ts +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { Action } from 'redux'; - -import { NewTrustedApp, TrustedApp } from '../../../../../common/endpoint/types'; -import { AsyncResourceState, TrustedAppsListData } from '../state'; -import { GetPolicyListResponse } from '../../policy/types'; - -export type TrustedAppsListDataOutdated = Action<'trustedAppsListDataOutdated'>; - -interface ResourceStateChanged extends Action { - payload: { newState: AsyncResourceState }; -} - -export type TrustedAppsListResourceStateChanged = ResourceStateChanged< - 'trustedAppsListResourceStateChanged', - TrustedAppsListData ->; - -export type TrustedAppDeletionSubmissionResourceStateChanged = - ResourceStateChanged<'trustedAppDeletionSubmissionResourceStateChanged'>; - -export type TrustedAppDeletionDialogStarted = Action<'trustedAppDeletionDialogStarted'> & { - payload: { - entry: TrustedApp; - }; -}; - -export type TrustedAppDeletionDialogConfirmed = Action<'trustedAppDeletionDialogConfirmed'>; - -export type TrustedAppDeletionDialogClosed = Action<'trustedAppDeletionDialogClosed'>; - -export type TrustedAppCreationSubmissionResourceStateChanged = ResourceStateChanged< - 'trustedAppCreationSubmissionResourceStateChanged', - TrustedApp ->; - -export type TrustedAppCreationDialogStarted = Action<'trustedAppCreationDialogStarted'> & { - payload: { - entry: NewTrustedApp; - }; -}; - -export type TrustedAppCreationDialogFormStateUpdated = - Action<'trustedAppCreationDialogFormStateUpdated'> & { - payload: { - entry: NewTrustedApp; - isValid: boolean; - }; - }; - -export type TrustedAppCreationEditItemStateChanged = - Action<'trustedAppCreationEditItemStateChanged'> & { - payload: AsyncResourceState; - }; - -export type TrustedAppCreationDialogConfirmed = Action<'trustedAppCreationDialogConfirmed'>; - -export type TrustedAppCreationDialogClosed = Action<'trustedAppCreationDialogClosed'>; - -export type TrustedAppsExistResponse = Action<'trustedAppsExistStateChanged'> & { - payload: AsyncResourceState; -}; - -export type TrustedAppsPoliciesStateChanged = Action<'trustedAppsPoliciesStateChanged'> & { - payload: AsyncResourceState; -}; - -export type TrustedAppForceRefresh = Action<'trustedAppForceRefresh'> & { - payload: { - forceRefresh: boolean; - }; -}; - -export type TrustedAppsPageAction = - | TrustedAppsListDataOutdated - | TrustedAppsListResourceStateChanged - | TrustedAppDeletionSubmissionResourceStateChanged - | TrustedAppDeletionDialogStarted - | TrustedAppDeletionDialogConfirmed - | TrustedAppDeletionDialogClosed - | TrustedAppCreationSubmissionResourceStateChanged - | TrustedAppCreationEditItemStateChanged - | TrustedAppCreationDialogStarted - | TrustedAppCreationDialogFormStateUpdated - | TrustedAppCreationDialogConfirmed - | TrustedAppsExistResponse - | TrustedAppsPoliciesStateChanged - | TrustedAppCreationDialogClosed - | TrustedAppForceRefresh; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/builders.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/builders.ts deleted file mode 100644 index f0fab98188927..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/builders.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { ConditionEntryField, OperatingSystem } from '@kbn/securitysolution-utils'; -import { TrustedAppConditionEntry, NewTrustedApp } from '../../../../../common/endpoint/types'; - -import { MANAGEMENT_DEFAULT_PAGE, MANAGEMENT_DEFAULT_PAGE_SIZE } from '../../../common/constants'; - -import { TrustedAppsListPageState } from '../state'; - -export const defaultConditionEntry = (): TrustedAppConditionEntry => ({ - field: ConditionEntryField.HASH, - operator: 'included', - type: 'match', - value: '', -}); - -export const defaultNewTrustedApp = (): NewTrustedApp => ({ - name: '', - os: OperatingSystem.WINDOWS, - entries: [defaultConditionEntry()], - description: '', - effectScope: { type: 'global' }, -}); - -export const initialDeletionDialogState = (): TrustedAppsListPageState['deletionDialog'] => ({ - confirmed: false, - submissionResourceState: { type: 'UninitialisedResourceState' }, -}); - -export const initialCreationDialogState = (): TrustedAppsListPageState['creationDialog'] => ({ - confirmed: false, - submissionResourceState: { type: 'UninitialisedResourceState' }, -}); - -export const initialTrustedAppsPageState = (): TrustedAppsListPageState => ({ - entriesExist: { type: 'UninitialisedResourceState' }, - listView: { - listResourceState: { type: 'UninitialisedResourceState' }, - freshDataTimestamp: Date.now(), - }, - deletionDialog: initialDeletionDialogState(), - creationDialog: initialCreationDialogState(), - policies: { type: 'UninitialisedResourceState' }, - location: { - page_index: MANAGEMENT_DEFAULT_PAGE, - page_size: MANAGEMENT_DEFAULT_PAGE_SIZE, - show: undefined, - id: undefined, - view_type: 'grid', - filter: '', - included_policies: '', - }, - active: false, - forceRefresh: false, -}); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts deleted file mode 100644 index 4455baddb047c..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts +++ /dev/null @@ -1,459 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { applyMiddleware, createStore } from 'redux'; - -import { createSpyMiddleware } from '../../../../common/store/test_utils'; - -import { - createDefaultPagination, - createListLoadedResourceState, - createLoadedListViewWithPagination, - createSampleTrustedApp, - createSampleTrustedApps, - createServerApiError, - createUninitialisedResourceState, - createUserChangedUrlAction, -} from '../test_utils'; - -import { TrustedAppsService } from '../service'; -import { Pagination, TrustedAppsListPageLocation, TrustedAppsListPageState } from '../state'; -import { initialTrustedAppsPageState } from './builders'; -import { trustedAppsPageReducer } from './reducer'; -import { createTrustedAppsPageMiddleware } from './middleware'; -import { Immutable } from '../../../../../common/endpoint/types'; -import { getGeneratedPolicyResponse } from './mocks'; - -const initialNow = 111111; -const dateNowMock = jest.fn(); -dateNowMock.mockReturnValue(initialNow); - -Date.now = dateNowMock; - -const initialState: Immutable = initialTrustedAppsPageState(); - -const createGetTrustedListAppsResponse = (pagination: Partial) => { - const fullPagination = { ...createDefaultPagination(), ...pagination }; - - return { - data: createSampleTrustedApps(pagination), - page: fullPagination.pageIndex, - per_page: fullPagination.pageSize, - total: fullPagination.totalItemCount, - }; -}; - -const createTrustedAppsServiceMock = (): jest.Mocked => ({ - getTrustedAppsList: jest.fn(), - deleteTrustedApp: jest.fn(), - createTrustedApp: jest.fn(), - getPolicyList: jest.fn(), - updateTrustedApp: jest.fn(), - getTrustedApp: jest.fn(), - assignPolicyToTrustedApps: jest.fn(), - removePolicyFromTrustedApps: jest.fn(), -}); - -const createStoreSetup = (trustedAppsService: TrustedAppsService) => { - const spyMiddleware = createSpyMiddleware(); - - return { - spyMiddleware, - store: createStore( - trustedAppsPageReducer, - applyMiddleware( - createTrustedAppsPageMiddleware(trustedAppsService), - spyMiddleware.actionSpyMiddleware - ) - ), - }; -}; - -describe('middleware', () => { - type TrustedAppsEntriesExistState = Pick; - const entriesExistLoadedState = (): TrustedAppsEntriesExistState => { - return { - entriesExist: { - data: true, - type: 'LoadedResourceState', - }, - }; - }; - const entriesExistLoadingState = (): TrustedAppsEntriesExistState => { - return { - entriesExist: { - previousState: { - type: 'UninitialisedResourceState', - }, - type: 'LoadingResourceState', - }, - }; - }; - - const createLocationState = ( - params?: Partial - ): TrustedAppsListPageLocation => { - return { - ...initialState.location, - ...(params ?? {}), - }; - }; - - beforeEach(() => { - dateNowMock.mockReturnValue(initialNow); - }); - - describe('initial state', () => { - it('sets initial state properly', async () => { - expect(createStoreSetup(createTrustedAppsServiceMock()).store.getState()).toStrictEqual( - initialState - ); - }); - }); - - describe('refreshing list resource state', () => { - it('refreshes the list when location changes and data gets outdated', async () => { - const pagination = { pageIndex: 2, pageSize: 50 }; - const location = createLocationState({ - page_index: 2, - page_size: 50, - }); - const service = createTrustedAppsServiceMock(); - const { store, spyMiddleware } = createStoreSetup(service); - - service.getTrustedAppsList.mockResolvedValue(createGetTrustedListAppsResponse(pagination)); - - store.dispatch( - createUserChangedUrlAction('/administration/trusted_apps', '?page_index=2&page_size=50') - ); - - expect(store.getState()).toStrictEqual({ - ...initialState, - listView: { - listResourceState: { - type: 'LoadingResourceState', - previousState: createUninitialisedResourceState(), - }, - freshDataTimestamp: initialNow, - }, - active: true, - location, - }); - - await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); - - expect(store.getState()).toStrictEqual({ - ...initialState, - ...entriesExistLoadingState(), - listView: createLoadedListViewWithPagination(initialNow, pagination), - active: true, - location, - }); - }); - - it('does not refresh the list when location changes and data does not get outdated', async () => { - const pagination = { pageIndex: 2, pageSize: 50 }; - const location = createLocationState({ - page_index: 2, - page_size: 50, - }); - const service = createTrustedAppsServiceMock(); - const { store, spyMiddleware } = createStoreSetup(service); - - service.getTrustedAppsList.mockResolvedValue(createGetTrustedListAppsResponse(pagination)); - - store.dispatch( - createUserChangedUrlAction('/administration/trusted_apps', '?page_index=2&page_size=50') - ); - - await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); - - store.dispatch( - createUserChangedUrlAction('/administration/trusted_apps', '?page_index=2&page_size=50') - ); - - expect(service.getTrustedAppsList).toBeCalledTimes(2); - expect(store.getState()).toStrictEqual({ - ...initialState, - ...entriesExistLoadingState(), - listView: createLoadedListViewWithPagination(initialNow, pagination), - active: true, - location, - }); - }); - - it('refreshes the list when data gets outdated with and outdate action', async () => { - const newNow = 222222; - const pagination = { pageIndex: 0, pageSize: 10 }; - const location = createLocationState(); - const service = createTrustedAppsServiceMock(); - const { store, spyMiddleware } = createStoreSetup(service); - const policiesResponse = getGeneratedPolicyResponse(); - - service.getTrustedAppsList.mockResolvedValue(createGetTrustedListAppsResponse(pagination)); - service.getPolicyList.mockResolvedValue(policiesResponse); - - store.dispatch(createUserChangedUrlAction('/administration/trusted_apps')); - - await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); - - dateNowMock.mockReturnValue(newNow); - - store.dispatch({ type: 'trustedAppsListDataOutdated' }); - - expect(store.getState()).toStrictEqual({ - ...initialState, - ...entriesExistLoadingState(), - listView: { - listResourceState: { - type: 'LoadingResourceState', - previousState: createListLoadedResourceState(pagination, initialNow), - }, - freshDataTimestamp: newNow, - }, - active: true, - location, - }); - - await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); - await spyMiddleware.waitForAction('trustedAppsPoliciesStateChanged'); - - expect(store.getState()).toStrictEqual({ - ...initialState, - ...entriesExistLoadedState(), - policies: { - data: policiesResponse, - type: 'LoadedResourceState', - }, - listView: createLoadedListViewWithPagination(newNow, pagination), - active: true, - location, - }); - }); - - it('set list resource state to failed when failing to load data', async () => { - const service = createTrustedAppsServiceMock(); - const { store, spyMiddleware } = createStoreSetup(service); - - service.getTrustedAppsList.mockRejectedValue({ - body: createServerApiError('Internal Server Error'), - }); - - store.dispatch( - createUserChangedUrlAction('/administration/trusted_apps', '?page_index=2&page_size=50') - ); - - await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); - - expect(store.getState()).toStrictEqual({ - ...initialState, - ...entriesExistLoadingState(), - listView: { - listResourceState: { - type: 'FailedResourceState', - error: createServerApiError('Internal Server Error'), - lastLoadedState: undefined, - }, - freshDataTimestamp: initialNow, - }, - active: true, - location: createLocationState({ - page_index: 2, - page_size: 50, - }), - }); - - const infiniteLoopTest = async () => { - await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); - }; - - await expect(infiniteLoopTest).rejects.not.toBeNull(); - }); - }); - - describe('submitting deletion dialog', () => { - const newNow = 222222; - const entry = createSampleTrustedApp(3); - const notFoundError = createServerApiError('Not Found'); - const pagination = { pageIndex: 0, pageSize: 10 }; - const location = createLocationState(); - const getTrustedAppsListResponse = createGetTrustedListAppsResponse(pagination); - const listView = createLoadedListViewWithPagination(initialNow, pagination); - const listViewNew = createLoadedListViewWithPagination(newNow, pagination); - const testStartState = { - ...initialState, - ...entriesExistLoadingState(), - listView, - active: true, - location, - }; - - it('does not submit when entry is undefined', async () => { - const service = createTrustedAppsServiceMock(); - const { store, spyMiddleware } = createStoreSetup(service); - - service.getTrustedAppsList.mockResolvedValue(getTrustedAppsListResponse); - service.deleteTrustedApp.mockResolvedValue(); - - store.dispatch(createUserChangedUrlAction('/administration/trusted_apps')); - - await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); - - store.dispatch({ type: 'trustedAppDeletionDialogConfirmed' }); - - expect(store.getState()).toStrictEqual({ - ...testStartState, - deletionDialog: { ...testStartState.deletionDialog, confirmed: true }, - }); - }); - - it('submits successfully when entry is defined', async () => { - const service = createTrustedAppsServiceMock(); - const { store, spyMiddleware } = createStoreSetup(service); - const policiesResponse = getGeneratedPolicyResponse(); - - service.getTrustedAppsList.mockResolvedValue(getTrustedAppsListResponse); - service.deleteTrustedApp.mockResolvedValue(); - service.getPolicyList.mockResolvedValue(policiesResponse); - - store.dispatch(createUserChangedUrlAction('/administration/trusted_apps')); - - await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); - - dateNowMock.mockReturnValue(newNow); - - store.dispatch({ type: 'trustedAppDeletionDialogStarted', payload: { entry } }); - store.dispatch({ type: 'trustedAppDeletionDialogConfirmed' }); - - expect(store.getState()).toStrictEqual({ - ...testStartState, - deletionDialog: { - entry, - confirmed: true, - submissionResourceState: { - type: 'LoadingResourceState', - previousState: { type: 'UninitialisedResourceState' }, - }, - }, - }); - - await spyMiddleware.waitForAction('trustedAppDeletionSubmissionResourceStateChanged'); - await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); - - expect(store.getState()).toStrictEqual({ - ...testStartState, - ...entriesExistLoadedState(), - policies: { - data: policiesResponse, - type: 'LoadedResourceState', - }, - listView: listViewNew, - }); - expect(service.deleteTrustedApp).toBeCalledWith({ id: '3' }); - expect(service.deleteTrustedApp).toBeCalledTimes(1); - }); - - it('does not submit twice', async () => { - const service = createTrustedAppsServiceMock(); - const { store, spyMiddleware } = createStoreSetup(service); - const policiesResponse = getGeneratedPolicyResponse(); - - service.getTrustedAppsList.mockResolvedValue(getTrustedAppsListResponse); - service.deleteTrustedApp.mockResolvedValue(); - service.getPolicyList.mockResolvedValue(policiesResponse); - - store.dispatch(createUserChangedUrlAction('/administration/trusted_apps')); - - await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); - - dateNowMock.mockReturnValue(newNow); - - store.dispatch({ type: 'trustedAppDeletionDialogStarted', payload: { entry } }); - store.dispatch({ type: 'trustedAppDeletionDialogConfirmed' }); - store.dispatch({ type: 'trustedAppDeletionDialogConfirmed' }); - - expect(store.getState()).toStrictEqual({ - ...testStartState, - deletionDialog: { - entry, - confirmed: true, - submissionResourceState: { - type: 'LoadingResourceState', - previousState: { type: 'UninitialisedResourceState' }, - }, - }, - }); - - await spyMiddleware.waitForAction('trustedAppDeletionSubmissionResourceStateChanged'); - await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); - - expect(store.getState()).toStrictEqual({ - ...testStartState, - ...entriesExistLoadedState(), - policies: { - data: policiesResponse, - type: 'LoadedResourceState', - }, - listView: listViewNew, - }); - expect(service.deleteTrustedApp).toBeCalledWith({ id: '3' }); - expect(service.deleteTrustedApp).toBeCalledTimes(1); - }); - - it('does not submit when server response with failure', async () => { - const service = createTrustedAppsServiceMock(); - const { store, spyMiddleware } = createStoreSetup(service); - const policiesResponse = getGeneratedPolicyResponse(); - - service.getTrustedAppsList.mockResolvedValue(getTrustedAppsListResponse); - service.deleteTrustedApp.mockRejectedValue({ body: notFoundError }); - service.getPolicyList.mockResolvedValue(policiesResponse); - - store.dispatch(createUserChangedUrlAction('/administration/trusted_apps')); - - await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); - - store.dispatch({ type: 'trustedAppDeletionDialogStarted', payload: { entry } }); - store.dispatch({ type: 'trustedAppDeletionDialogConfirmed' }); - - expect(store.getState()).toStrictEqual({ - ...testStartState, - deletionDialog: { - entry, - confirmed: true, - submissionResourceState: { - type: 'LoadingResourceState', - previousState: { type: 'UninitialisedResourceState' }, - }, - }, - }); - - await spyMiddleware.waitForAction('trustedAppDeletionSubmissionResourceStateChanged'); - await spyMiddleware.waitForAction('trustedAppsPoliciesStateChanged'); - - expect(store.getState()).toStrictEqual({ - ...testStartState, - ...entriesExistLoadedState(), - policies: { - data: policiesResponse, - type: 'LoadedResourceState', - }, - deletionDialog: { - entry, - confirmed: true, - submissionResourceState: { - type: 'FailedResourceState', - error: notFoundError, - lastLoadedState: undefined, - }, - }, - }); - expect(service.deleteTrustedApp).toBeCalledWith({ id: '3' }); - expect(service.deleteTrustedApp).toBeCalledTimes(1); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts deleted file mode 100644 index cd39ee27353e9..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts +++ /dev/null @@ -1,477 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; -import { - Immutable, - PostTrustedAppCreateRequest, - TrustedApp, -} from '../../../../../common/endpoint/types'; -import { AppAction } from '../../../../common/store/actions'; -import { - ImmutableMiddleware, - ImmutableMiddlewareAPI, - ImmutableMiddlewareFactory, -} from '../../../../common/store'; - -import { TrustedAppsHttpService, TrustedAppsService } from '../service'; - -import { - AsyncResourceState, - getLastLoadedResourceState, - isLoadedResourceState, - isLoadingResourceState, - isStaleResourceState, - isUninitialisedResourceState, - StaleResourceState, - TrustedAppsListData, - TrustedAppsListPageState, -} from '../state'; - -import { defaultNewTrustedApp } from './builders'; - -import { - TrustedAppCreationSubmissionResourceStateChanged, - TrustedAppDeletionSubmissionResourceStateChanged, - TrustedAppsListResourceStateChanged, -} from './action'; - -import { - getListResourceState, - getDeletionDialogEntry, - getDeletionSubmissionResourceState, - getLastLoadedListResourceState, - getCurrentLocationPageIndex, - getCurrentLocationPageSize, - getCurrentLocationFilter, - needsRefreshOfListData, - getCreationSubmissionResourceState, - getCreationDialogFormEntry, - isCreationDialogLocation, - isCreationDialogFormValid, - entriesExist, - getListTotalItemsCount, - trustedAppsListPageActive, - entriesExistState, - policiesState, - isEdit, - isFetchingEditTrustedAppItem, - editItemId, - editingTrustedApp, - getListItems, - getCurrentLocationIncludedPolicies, -} from './selectors'; -import { parsePoliciesToKQL, parseQueryFilterToKQL } from '../../../common/utils'; -import { toUpdateTrustedApp } from '../../../../../common/endpoint/service/trusted_apps/to_update_trusted_app'; -import { SEARCHABLE_FIELDS } from '../constants'; - -const createTrustedAppsListResourceStateChangedAction = ( - newState: Immutable> -): Immutable => ({ - type: 'trustedAppsListResourceStateChanged', - payload: { newState }, -}); - -const refreshListIfNeeded = async ( - store: ImmutableMiddlewareAPI, - trustedAppsService: TrustedAppsService -) => { - if (needsRefreshOfListData(store.getState())) { - store.dispatch({ type: 'trustedAppForceRefresh', payload: { forceRefresh: false } }); - store.dispatch( - createTrustedAppsListResourceStateChangedAction({ - type: 'LoadingResourceState', - // need to think on how to avoid the casting - previousState: getListResourceState(store.getState()) as Immutable< - StaleResourceState - >, - }) - ); - - try { - const pageIndex = getCurrentLocationPageIndex(store.getState()); - const pageSize = getCurrentLocationPageSize(store.getState()); - const filter = getCurrentLocationFilter(store.getState()); - const includedPolicies = getCurrentLocationIncludedPolicies(store.getState()); - - const kuery = []; - - const filterKuery = parseQueryFilterToKQL(filter, SEARCHABLE_FIELDS) || undefined; - if (filterKuery) kuery.push(filterKuery); - - const policiesKuery = - parsePoliciesToKQL(includedPolicies ? includedPolicies.split(',') : []) || undefined; - if (policiesKuery) kuery.push(policiesKuery); - - const response = await trustedAppsService.getTrustedAppsList({ - page: pageIndex + 1, - per_page: pageSize, - kuery: kuery.join(' AND ') || undefined, - }); - - store.dispatch( - createTrustedAppsListResourceStateChangedAction({ - type: 'LoadedResourceState', - data: { - items: response.data, - pageIndex, - pageSize, - totalItemsCount: response.total, - timestamp: Date.now(), - filter, - includedPolicies, - }, - }) - ); - } catch (error) { - store.dispatch( - createTrustedAppsListResourceStateChangedAction({ - type: 'FailedResourceState', - error: error.body, - lastLoadedState: getLastLoadedListResourceState(store.getState()), - }) - ); - } - } -}; - -const updateCreationDialogIfNeeded = ( - store: ImmutableMiddlewareAPI -) => { - const newEntry = getCreationDialogFormEntry(store.getState()); - const shouldShow = isCreationDialogLocation(store.getState()); - - if (shouldShow && !newEntry) { - store.dispatch({ - type: 'trustedAppCreationDialogStarted', - payload: { entry: defaultNewTrustedApp() }, - }); - } else if (!shouldShow && newEntry) { - store.dispatch({ - type: 'trustedAppCreationDialogClosed', - }); - } -}; - -const createTrustedAppCreationSubmissionResourceStateChanged = ( - newState: Immutable> -): Immutable => ({ - type: 'trustedAppCreationSubmissionResourceStateChanged', - payload: { newState }, -}); - -const submitCreationIfNeeded = async ( - store: ImmutableMiddlewareAPI, - trustedAppsService: TrustedAppsService -) => { - const currentState = store.getState(); - const submissionResourceState = getCreationSubmissionResourceState(currentState); - const isValid = isCreationDialogFormValid(currentState); - const entry = getCreationDialogFormEntry(currentState); - const editMode = isEdit(currentState); - - if (isStaleResourceState(submissionResourceState) && entry !== undefined && isValid) { - store.dispatch( - createTrustedAppCreationSubmissionResourceStateChanged({ - type: 'LoadingResourceState', - previousState: submissionResourceState, - }) - ); - - try { - let responseTrustedApp: TrustedApp; - - if (editMode) { - responseTrustedApp = ( - await trustedAppsService.updateTrustedApp( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - { id: editItemId(currentState)! }, - // TODO: try to remove the cast - entry as PostTrustedAppCreateRequest - ) - ).data; - } else { - // TODO: try to remove the cast - responseTrustedApp = ( - await trustedAppsService.createTrustedApp(entry as PostTrustedAppCreateRequest) - ).data; - } - - store.dispatch( - createTrustedAppCreationSubmissionResourceStateChanged({ - type: 'LoadedResourceState', - data: responseTrustedApp, - }) - ); - store.dispatch({ - type: 'trustedAppsListDataOutdated', - }); - } catch (error) { - store.dispatch( - createTrustedAppCreationSubmissionResourceStateChanged({ - type: 'FailedResourceState', - error: error.body, - lastLoadedState: getLastLoadedResourceState(submissionResourceState), - }) - ); - } - } -}; - -const createTrustedAppDeletionSubmissionResourceStateChanged = ( - newState: Immutable -): Immutable => ({ - type: 'trustedAppDeletionSubmissionResourceStateChanged', - payload: { newState }, -}); - -const submitDeletionIfNeeded = async ( - store: ImmutableMiddlewareAPI, - trustedAppsService: TrustedAppsService -) => { - const submissionResourceState = getDeletionSubmissionResourceState(store.getState()); - const entry = getDeletionDialogEntry(store.getState()); - - if (isStaleResourceState(submissionResourceState) && entry !== undefined) { - store.dispatch( - createTrustedAppDeletionSubmissionResourceStateChanged({ - type: 'LoadingResourceState', - previousState: submissionResourceState, - }) - ); - - try { - await trustedAppsService.deleteTrustedApp({ id: entry.id }); - - store.dispatch( - createTrustedAppDeletionSubmissionResourceStateChanged({ - type: 'LoadedResourceState', - data: null, - }) - ); - store.dispatch({ - type: 'trustedAppDeletionDialogClosed', - }); - store.dispatch({ - type: 'trustedAppsListDataOutdated', - }); - } catch (error) { - store.dispatch( - createTrustedAppDeletionSubmissionResourceStateChanged({ - type: 'FailedResourceState', - error: error.body, - lastLoadedState: getLastLoadedResourceState(submissionResourceState), - }) - ); - } - } -}; - -const checkTrustedAppsExistIfNeeded = async ( - store: ImmutableMiddlewareAPI, - trustedAppsService: TrustedAppsService -) => { - const currentState = store.getState(); - const currentEntriesExistState = entriesExistState(currentState); - - if ( - trustedAppsListPageActive(currentState) && - !isLoadingResourceState(currentEntriesExistState) - ) { - const currentListTotal = getListTotalItemsCount(currentState); - const currentDoEntriesExist = entriesExist(currentState); - - if ( - !isLoadedResourceState(currentEntriesExistState) || - (currentListTotal === 0 && currentDoEntriesExist) || - (currentListTotal > 0 && !currentDoEntriesExist) - ) { - store.dispatch({ - type: 'trustedAppsExistStateChanged', - payload: { type: 'LoadingResourceState', previousState: currentEntriesExistState }, - }); - - let doTheyExist: boolean; - try { - const { total } = await trustedAppsService.getTrustedAppsList({ - page: 1, - per_page: 1, - }); - doTheyExist = total > 0; - } catch (e) { - // If a failure occurs, lets assume entries exits so that the UI is not blocked to the user - doTheyExist = true; - } - - store.dispatch({ - type: 'trustedAppsExistStateChanged', - payload: { type: 'LoadedResourceState', data: doTheyExist }, - }); - } - } -}; - -export const retrieveListOfPoliciesIfNeeded = async ( - { getState, dispatch }: ImmutableMiddlewareAPI, - trustedAppsService: TrustedAppsService -) => { - const currentState = getState(); - const currentPoliciesState = policiesState(currentState); - const isLoading = isLoadingResourceState(currentPoliciesState); - const isPageActive = trustedAppsListPageActive(currentState); - const isCreateFlow = isCreationDialogLocation(currentState); - const isUninitialized = isUninitialisedResourceState(currentPoliciesState); - - if (isPageActive && ((isCreateFlow && !isLoading) || isUninitialized)) { - dispatch({ - type: 'trustedAppsPoliciesStateChanged', - payload: { - type: 'LoadingResourceState', - previousState: currentPoliciesState, - } as TrustedAppsListPageState['policies'], - }); - - try { - const policyList = await trustedAppsService.getPolicyList({ - query: { - page: 1, - perPage: 1000, - }, - }); - - dispatch({ - type: 'trustedAppsPoliciesStateChanged', - payload: { - type: 'LoadedResourceState', - data: policyList, - }, - }); - } catch (error) { - dispatch({ - type: 'trustedAppsPoliciesStateChanged', - payload: { - type: 'FailedResourceState', - error: error.body || error, - lastLoadedState: getLastLoadedResourceState(policiesState(getState())), - }, - }); - } - } -}; - -const fetchEditTrustedAppIfNeeded = async ( - { getState, dispatch }: ImmutableMiddlewareAPI, - trustedAppsService: TrustedAppsService -) => { - const currentState = getState(); - const isPageActive = trustedAppsListPageActive(currentState); - const isEditFlow = isEdit(currentState); - const isAlreadyFetching = isFetchingEditTrustedAppItem(currentState); - const editTrustedAppId = editItemId(currentState); - - if (isPageActive && isEditFlow && !isAlreadyFetching) { - if (!editTrustedAppId) { - const errorMessage = i18n.translate( - 'xpack.securitySolution.trustedapps.middleware.editIdMissing', - { - defaultMessage: 'No id provided', - } - ); - - dispatch({ - type: 'trustedAppCreationEditItemStateChanged', - payload: { - type: 'FailedResourceState', - error: Object.assign(new Error(errorMessage), { statusCode: 404, error: errorMessage }), - }, - }); - return; - } - - let trustedAppForEdit = editingTrustedApp(currentState); - - // If Trusted App is already loaded, then do nothing - if (trustedAppForEdit && trustedAppForEdit.id === editTrustedAppId) { - return; - } - - // See if we can get the Trusted App record from the current list of Trusted Apps being displayed - trustedAppForEdit = getListItems(currentState).find((ta) => ta.id === editTrustedAppId); - - try { - // Retrieve Trusted App record via API if it was not in the list data. - // This would be the case when linking from another place or using an UUID for a Trusted App - // that is not currently displayed on the list view. - if (!trustedAppForEdit) { - dispatch({ - type: 'trustedAppCreationEditItemStateChanged', - payload: { - type: 'LoadingResourceState', - }, - }); - - trustedAppForEdit = (await trustedAppsService.getTrustedApp({ id: editTrustedAppId })).data; - } - - dispatch({ - type: 'trustedAppCreationEditItemStateChanged', - payload: { - type: 'LoadedResourceState', - data: trustedAppForEdit, - }, - }); - - dispatch({ - type: 'trustedAppCreationDialogFormStateUpdated', - payload: { - entry: toUpdateTrustedApp(trustedAppForEdit), - isValid: true, - }, - }); - } catch (e) { - dispatch({ - type: 'trustedAppCreationEditItemStateChanged', - payload: { - type: 'FailedResourceState', - error: e, - }, - }); - } - } -}; - -export const createTrustedAppsPageMiddleware = ( - trustedAppsService: TrustedAppsService -): ImmutableMiddleware => { - return (store) => (next) => async (action) => { - next(action); - - // TODO: need to think if failed state is a good condition to consider need for refresh - if (action.type === 'userChangedUrl' || action.type === 'trustedAppsListDataOutdated') { - await refreshListIfNeeded(store, trustedAppsService); - await checkTrustedAppsExistIfNeeded(store, trustedAppsService); - } - - if (action.type === 'userChangedUrl') { - updateCreationDialogIfNeeded(store); - retrieveListOfPoliciesIfNeeded(store, trustedAppsService); - fetchEditTrustedAppIfNeeded(store, trustedAppsService); - } - - if (action.type === 'trustedAppCreationDialogConfirmed') { - await submitCreationIfNeeded(store, trustedAppsService); - } - - if (action.type === 'trustedAppDeletionDialogConfirmed') { - await submitDeletionIfNeeded(store, trustedAppsService); - } - }; -}; - -export const trustedAppsPageMiddlewareFactory: ImmutableMiddlewareFactory< - TrustedAppsListPageState -> = (coreStart) => createTrustedAppsPageMiddleware(new TrustedAppsHttpService(coreStart.http)); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/mocks.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/mocks.ts deleted file mode 100644 index c97dd37db6bbf..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/mocks.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { GetPolicyListResponse } from '../../policy/types'; - -import { EndpointDocGenerator } from '../../../../../common/endpoint/generate_data'; - -export const getGeneratedPolicyResponse = (): GetPolicyListResponse => ({ - items: [new EndpointDocGenerator('seed').generatePolicyPackagePolicy()], - total: 1, - perPage: 1, - page: 1, -}); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts deleted file mode 100644 index 5047608cbb4ec..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { AsyncResourceState } from '../state'; -import { initialTrustedAppsPageState } from './builders'; -import { trustedAppsPageReducer } from './reducer'; -import { - createSampleTrustedApp, - createListLoadedResourceState, - createLoadedListViewWithPagination, - createUserChangedUrlAction, - createTrustedAppsListResourceStateChangedAction, -} from '../test_utils'; - -const initialNow = 111111; -const dateNowMock = jest.fn(); -dateNowMock.mockReturnValue(initialNow); - -Date.now = dateNowMock; - -const initialState = initialTrustedAppsPageState(); - -describe('reducer', () => { - describe('UserChangedUrl', () => { - it('makes page state active and extracts all parameters', () => { - const result = trustedAppsPageReducer( - initialState, - createUserChangedUrlAction( - '/administration/trusted_apps', - '?page_index=5&page_size=50&show=create&view_type=list&filter=test&included_policies=global' - ) - ); - - expect(result).toStrictEqual({ - ...initialState, - location: { - page_index: 5, - page_size: 50, - show: 'create', - view_type: 'list', - id: undefined, - filter: 'test', - included_policies: 'global', - }, - active: true, - }); - }); - - it('extracts default pagination parameters when invalid provided', () => { - const result = trustedAppsPageReducer( - { - ...initialState, - location: { - page_index: 5, - page_size: 50, - view_type: 'grid', - filter: '', - included_policies: '', - }, - }, - createUserChangedUrlAction( - '/administration/trusted_apps', - '?page_index=b&page_size=60&show=a&view_type=c' - ) - ); - - expect(result).toStrictEqual({ ...initialState, active: true }); - }); - - it('extracts default pagination parameters when none provided', () => { - const result = trustedAppsPageReducer( - { - ...initialState, - location: { - page_index: 5, - page_size: 50, - view_type: 'grid', - filter: '', - included_policies: '', - }, - }, - createUserChangedUrlAction('/administration/trusted_apps') - ); - - expect(result).toStrictEqual({ ...initialState, active: true }); - }); - - it('makes page state inactive and resets list to uninitialised state when navigating away', () => { - const result = trustedAppsPageReducer( - { ...initialState, listView: createLoadedListViewWithPagination(initialNow), active: true }, - createUserChangedUrlAction('/administration/endpoints') - ); - - expect(result).toStrictEqual(initialState); - }); - }); - - describe('TrustedAppsListResourceStateChanged', () => { - it('sets the current list resource state', () => { - const listResourceState = createListLoadedResourceState( - { pageIndex: 3, pageSize: 50 }, - initialNow - ); - const result = trustedAppsPageReducer( - initialState, - createTrustedAppsListResourceStateChangedAction(listResourceState) - ); - - expect(result).toStrictEqual({ - ...initialState, - listView: { ...initialState.listView, listResourceState }, - }); - }); - }); - - describe('TrustedAppsListDataOutdated', () => { - it('sets the list view freshness timestamp', () => { - const newNow = 222222; - dateNowMock.mockReturnValue(newNow); - - const result = trustedAppsPageReducer(initialState, { type: 'trustedAppsListDataOutdated' }); - - expect(result).toStrictEqual({ - ...initialState, - listView: { ...initialState.listView, freshDataTimestamp: newNow }, - }); - }); - }); - - describe('TrustedAppDeletionSubmissionResourceStateChanged', () => { - it('sets the deletion dialog submission resource state', () => { - const submissionResourceState: AsyncResourceState = { - type: 'LoadedResourceState', - data: null, - }; - const result = trustedAppsPageReducer(initialState, { - type: 'trustedAppDeletionSubmissionResourceStateChanged', - payload: { newState: submissionResourceState }, - }); - - expect(result).toStrictEqual({ - ...initialState, - deletionDialog: { ...initialState.deletionDialog, submissionResourceState }, - }); - }); - }); - - describe('TrustedAppDeletionDialogStarted', () => { - it('sets the deletion dialog state to started', () => { - const entry = createSampleTrustedApp(3); - const result = trustedAppsPageReducer(initialState, { - type: 'trustedAppDeletionDialogStarted', - payload: { entry }, - }); - - expect(result).toStrictEqual({ - ...initialState, - deletionDialog: { ...initialState.deletionDialog, entry }, - }); - }); - }); - - describe('TrustedAppDeletionDialogConfirmed', () => { - it('sets the deletion dialog state to confirmed', () => { - const entry = createSampleTrustedApp(3); - const result = trustedAppsPageReducer( - { - ...initialState, - deletionDialog: { - entry, - confirmed: false, - submissionResourceState: { type: 'UninitialisedResourceState' }, - }, - }, - { type: 'trustedAppDeletionDialogConfirmed' } - ); - - expect(result).toStrictEqual({ - ...initialState, - deletionDialog: { - entry, - confirmed: true, - submissionResourceState: { type: 'UninitialisedResourceState' }, - }, - }); - }); - }); - - describe('TrustedAppDeletionDialogClosed', () => { - it('sets the deletion dialog state to confirmed', () => { - const result = trustedAppsPageReducer( - { - ...initialState, - deletionDialog: { - entry: createSampleTrustedApp(3), - confirmed: true, - submissionResourceState: { type: 'UninitialisedResourceState' }, - }, - }, - { type: 'trustedAppDeletionDialogClosed' } - ); - - expect(result).toStrictEqual(initialState); - }); - }); - - describe('TrustedAppsForceRefresh', () => { - it('sets the force refresh state to true', () => { - const result = trustedAppsPageReducer( - { - ...initialState, - forceRefresh: false, - }, - { type: 'trustedAppForceRefresh', payload: { forceRefresh: true } } - ); - - expect(result).toStrictEqual({ ...initialState, forceRefresh: true }); - }); - it('sets the force refresh state to false', () => { - const result = trustedAppsPageReducer( - { - ...initialState, - forceRefresh: true, - }, - { type: 'trustedAppForceRefresh', payload: { forceRefresh: false } } - ); - - expect(result).toStrictEqual({ ...initialState, forceRefresh: false }); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.ts deleted file mode 100644 index 07409a46156db..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.ts +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -// eslint-disable-next-line import/no-nodejs-modules -import { parse } from 'querystring'; -import { matchPath } from 'react-router-dom'; -import { ImmutableReducer } from '../../../../common/store'; -import { AppLocation, Immutable } from '../../../../../common/endpoint/types'; -import { UserChangedUrl } from '../../../../common/store/routing/action'; -import { AppAction } from '../../../../common/store/actions'; -import { extractTrustedAppsListPageLocation } from '../../../common/routing'; - -import { MANAGEMENT_ROUTING_TRUSTED_APPS_PATH } from '../../../common/constants'; - -import { - TrustedAppDeletionDialogClosed, - TrustedAppDeletionDialogConfirmed, - TrustedAppDeletionDialogStarted, - TrustedAppDeletionSubmissionResourceStateChanged, - TrustedAppCreationSubmissionResourceStateChanged, - TrustedAppsListDataOutdated, - TrustedAppsListResourceStateChanged, - TrustedAppCreationDialogStarted, - TrustedAppCreationDialogFormStateUpdated, - TrustedAppCreationDialogConfirmed, - TrustedAppCreationDialogClosed, - TrustedAppsExistResponse, - TrustedAppsPoliciesStateChanged, - TrustedAppCreationEditItemStateChanged, - TrustedAppForceRefresh, -} from './action'; - -import { TrustedAppsListPageState } from '../state'; -import { - initialCreationDialogState, - initialDeletionDialogState, - initialTrustedAppsPageState, -} from './builders'; -import { entriesExistState, trustedAppsListPageActive } from './selectors'; - -type StateReducer = ImmutableReducer; -type CaseReducer = ( - state: Immutable, - action: Immutable -) => Immutable; - -const isTrustedAppsPageLocation = (location: Immutable) => { - return ( - matchPath(location.pathname ?? '', { - path: MANAGEMENT_ROUTING_TRUSTED_APPS_PATH, - exact: true, - }) !== null - ); -}; - -const trustedAppsListDataOutdated: CaseReducer = (state, action) => { - return { ...state, listView: { ...state.listView, freshDataTimestamp: Date.now() } }; -}; - -const trustedAppsListResourceStateChanged: CaseReducer = ( - state, - action -) => { - return { ...state, listView: { ...state.listView, listResourceState: action.payload.newState } }; -}; - -const trustedAppDeletionSubmissionResourceStateChanged: CaseReducer< - TrustedAppDeletionSubmissionResourceStateChanged -> = (state, action) => { - return { - ...state, - deletionDialog: { ...state.deletionDialog, submissionResourceState: action.payload.newState }, - }; -}; - -const trustedAppDeletionDialogStarted: CaseReducer = ( - state, - action -) => { - return { ...state, deletionDialog: { ...initialDeletionDialogState(), ...action.payload } }; -}; - -const trustedAppDeletionDialogConfirmed: CaseReducer = ( - state -) => { - return { ...state, deletionDialog: { ...state.deletionDialog, confirmed: true } }; -}; - -const trustedAppDeletionDialogClosed: CaseReducer = (state) => { - return { ...state, deletionDialog: initialDeletionDialogState() }; -}; - -const trustedAppCreationSubmissionResourceStateChanged: CaseReducer< - TrustedAppCreationSubmissionResourceStateChanged -> = (state, action) => { - return { - ...state, - creationDialog: { ...state.creationDialog, submissionResourceState: action.payload.newState }, - }; -}; - -const trustedAppCreationDialogStarted: CaseReducer = ( - state, - action -) => { - return { - ...state, - creationDialog: { - ...initialCreationDialogState(), - formState: { ...action.payload, isValid: false }, - }, - }; -}; - -const trustedAppCreationDialogFormStateUpdated: CaseReducer< - TrustedAppCreationDialogFormStateUpdated -> = (state, action) => { - return { - ...state, - creationDialog: { ...state.creationDialog, formState: { ...action.payload } }, - }; -}; - -const handleUpdateToEditItemState: CaseReducer = ( - state, - action -) => { - return { - ...state, - creationDialog: { ...state.creationDialog, editItem: action.payload }, - }; -}; - -const trustedAppCreationDialogConfirmed: CaseReducer = ( - state -) => { - return { ...state, creationDialog: { ...state.creationDialog, confirmed: true } }; -}; - -const trustedAppCreationDialogClosed: CaseReducer = (state) => { - return { ...state, creationDialog: initialCreationDialogState() }; -}; - -const userChangedUrl: CaseReducer = (state, action) => { - if (isTrustedAppsPageLocation(action.payload)) { - const location = extractTrustedAppsListPageLocation(parse(action.payload.search.slice(1))); - - return { ...state, active: true, location }; - } else { - return initialTrustedAppsPageState(); - } -}; - -const updateEntriesExists: CaseReducer = (state, { payload }) => { - if (entriesExistState(state) !== payload) { - return { - ...state, - entriesExist: payload, - }; - } - return state; -}; - -const updatePolicies: CaseReducer = (state, { payload }) => { - if (trustedAppsListPageActive(state)) { - return { - ...state, - policies: payload, - }; - } - return state; -}; - -const forceRefresh: CaseReducer = (state, { payload }) => { - return { - ...state, - forceRefresh: payload.forceRefresh, - }; -}; - -export const trustedAppsPageReducer: StateReducer = ( - state = initialTrustedAppsPageState(), - action -) => { - switch (action.type) { - case 'trustedAppsListDataOutdated': - return trustedAppsListDataOutdated(state, action); - - case 'trustedAppsListResourceStateChanged': - return trustedAppsListResourceStateChanged(state, action); - - case 'trustedAppDeletionSubmissionResourceStateChanged': - return trustedAppDeletionSubmissionResourceStateChanged(state, action); - - case 'trustedAppDeletionDialogStarted': - return trustedAppDeletionDialogStarted(state, action); - - case 'trustedAppDeletionDialogConfirmed': - return trustedAppDeletionDialogConfirmed(state, action); - - case 'trustedAppDeletionDialogClosed': - return trustedAppDeletionDialogClosed(state, action); - - case 'trustedAppCreationSubmissionResourceStateChanged': - return trustedAppCreationSubmissionResourceStateChanged(state, action); - - case 'trustedAppCreationDialogStarted': - return trustedAppCreationDialogStarted(state, action); - - case 'trustedAppCreationDialogFormStateUpdated': - return trustedAppCreationDialogFormStateUpdated(state, action); - - case 'trustedAppCreationEditItemStateChanged': - return handleUpdateToEditItemState(state, action); - - case 'trustedAppCreationDialogConfirmed': - return trustedAppCreationDialogConfirmed(state, action); - - case 'trustedAppCreationDialogClosed': - return trustedAppCreationDialogClosed(state, action); - - case 'userChangedUrl': - return userChangedUrl(state, action); - - case 'trustedAppsExistStateChanged': - return updateEntriesExists(state, action); - - case 'trustedAppsPoliciesStateChanged': - return updatePolicies(state, action); - - case 'trustedAppForceRefresh': - return forceRefresh(state, action); - } - - return state; -}; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.test.ts deleted file mode 100644 index 4468d044827c0..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.test.ts +++ /dev/null @@ -1,388 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - AsyncResourceState, - TrustedAppsListPageLocation, - TrustedAppsListPageState, -} from '../state'; -import { initialTrustedAppsPageState } from './builders'; -import { - getListResourceState, - getLastLoadedListResourceState, - getCurrentLocationPageIndex, - getCurrentLocationPageSize, - getListErrorMessage, - getListItems, - getListTotalItemsCount, - isListLoading, - needsRefreshOfListData, - isDeletionDialogOpen, - isDeletionInProgress, - isDeletionSuccessful, - getDeletionError, - getDeletionDialogEntry, - getDeletionSubmissionResourceState, -} from './selectors'; - -import { - createDefaultPagination, - createListComplexLoadingResourceState, - createListFailedResourceState, - createListLoadedResourceState, - createLoadedListViewWithPagination, - createSampleTrustedApp, - createSampleTrustedApps, - createServerApiError, - createUninitialisedResourceState, -} from '../test_utils'; - -const initialNow = 111111; -const dateNowMock = jest.fn(); -dateNowMock.mockReturnValue(initialNow); - -Date.now = dateNowMock; - -const initialState = initialTrustedAppsPageState(); - -const createStateWithDeletionSubmissionResourceState = ( - submissionResourceState: AsyncResourceState -): TrustedAppsListPageState => ({ - ...initialState, - deletionDialog: { ...initialState.deletionDialog, submissionResourceState }, -}); - -describe('selectors', () => { - describe('needsRefreshOfListData()', () => { - it('returns false for outdated resource state and inactive state', () => { - expect(needsRefreshOfListData(initialState)).toBe(false); - }); - - it('returns true for outdated resource state and active state', () => { - expect(needsRefreshOfListData({ ...initialState, active: true })).toBe(true); - }); - - it('returns true when current loaded page index is outdated', () => { - const listView = createLoadedListViewWithPagination(initialNow, { pageIndex: 1 }); - - expect(needsRefreshOfListData({ ...initialState, listView, active: true })).toBe(true); - }); - - it('returns true when current loaded page size is outdated', () => { - const listView = createLoadedListViewWithPagination(initialNow, { pageSize: 50 }); - - expect(needsRefreshOfListData({ ...initialState, listView, active: true })).toBe(true); - }); - - it('returns true when current loaded data timestamp is outdated', () => { - const listView = { - ...createLoadedListViewWithPagination(111111), - freshDataTimestamp: 222222, - }; - - expect(needsRefreshOfListData({ ...initialState, listView, active: true })).toBe(true); - }); - - it('returns false when current loaded data is up to date', () => { - const listView = createLoadedListViewWithPagination(initialNow); - const location: TrustedAppsListPageLocation = { - page_index: 0, - page_size: 10, - view_type: 'grid', - filter: '', - included_policies: '', - }; - - expect(needsRefreshOfListData({ ...initialState, listView, active: true, location })).toBe( - false - ); - }); - }); - - describe('getListResourceState()', () => { - it('returns current list resource state', () => { - expect(getListResourceState(initialState)).toStrictEqual(createUninitialisedResourceState()); - }); - }); - - describe('getLastLoadedListResourceState()', () => { - it('returns last loaded list resource state', () => { - const state = { - ...initialState, - listView: { - listResourceState: createListComplexLoadingResourceState( - createDefaultPagination(), - initialNow - ), - freshDataTimestamp: initialNow, - }, - }; - - expect(getLastLoadedListResourceState(state)).toStrictEqual( - createListLoadedResourceState(createDefaultPagination(), initialNow) - ); - }); - }); - - describe('getListItems()', () => { - it('returns empty list when no valid data loaded', () => { - expect(getListItems(initialState)).toStrictEqual([]); - }); - - it('returns last loaded list items', () => { - const state = { - ...initialState, - listView: { - listResourceState: createListComplexLoadingResourceState( - createDefaultPagination(), - initialNow - ), - freshDataTimestamp: initialNow, - }, - }; - - expect(getListItems(state)).toStrictEqual(createSampleTrustedApps(createDefaultPagination())); - }); - }); - - describe('getListTotalItemsCount()', () => { - it('returns 0 when no valid data loaded', () => { - expect(getListTotalItemsCount(initialState)).toBe(0); - }); - - it('returns last loaded total items count', () => { - const state = { - ...initialState, - listView: { - listResourceState: createListComplexLoadingResourceState( - createDefaultPagination(), - initialNow - ), - freshDataTimestamp: initialNow, - }, - }; - - expect(getListTotalItemsCount(state)).toBe(200); - }); - }); - - describe('getListCurrentPageIndex()', () => { - it('returns page index', () => { - const location: TrustedAppsListPageLocation = { - page_index: 3, - page_size: 10, - view_type: 'grid', - filter: '', - included_policies: '', - }; - - expect(getCurrentLocationPageIndex({ ...initialState, location })).toBe(3); - }); - }); - - describe('getListCurrentPageSize()', () => { - it('returns page size', () => { - const location: TrustedAppsListPageLocation = { - page_index: 0, - page_size: 20, - view_type: 'grid', - filter: '', - included_policies: '', - }; - - expect(getCurrentLocationPageSize({ ...initialState, location })).toBe(20); - }); - }); - - describe('getListErrorMessage()', () => { - it('returns undefined when not in failed state', () => { - const state = { - ...initialState, - listView: { - listResourceState: createListComplexLoadingResourceState( - createDefaultPagination(), - initialNow - ), - freshDataTimestamp: initialNow, - }, - }; - - expect(getListErrorMessage(state)).toBeUndefined(); - }); - - it('returns message when not in failed state', () => { - const state = { - ...initialState, - listView: { - listResourceState: createListFailedResourceState('Internal Server Error'), - freshDataTimestamp: initialNow, - }, - }; - - expect(getListErrorMessage(state)).toBe('Internal Server Error'); - }); - }); - - describe('isListLoading()', () => { - it('returns false when no loading is happening', () => { - expect(isListLoading(initialState)).toBe(false); - }); - - it('returns true when loading is in progress', () => { - const state = { - ...initialState, - listView: { - listResourceState: createListComplexLoadingResourceState( - createDefaultPagination(), - initialNow - ), - freshDataTimestamp: initialNow, - }, - }; - - expect(isListLoading(state)).toBe(true); - }); - }); - - describe('isDeletionDialogOpen()', () => { - it('returns false when no entry is set', () => { - expect(isDeletionDialogOpen(initialState)).toBe(false); - }); - - it('returns true when entry is set', () => { - const state = { - ...initialState, - deletionDialog: { - ...initialState.deletionDialog, - entry: createSampleTrustedApp(5), - }, - }; - - expect(isDeletionDialogOpen(state)).toBe(true); - }); - }); - - describe('isDeletionInProgress()', () => { - it('returns false when resource state is uninitialised', () => { - expect(isDeletionInProgress(initialState)).toBe(false); - }); - - it('returns true when resource state is loading', () => { - const state = createStateWithDeletionSubmissionResourceState({ - type: 'LoadingResourceState', - previousState: { type: 'UninitialisedResourceState' }, - }); - - expect(isDeletionInProgress(state)).toBe(true); - }); - - it('returns false when resource state is loaded', () => { - const state = createStateWithDeletionSubmissionResourceState({ - type: 'LoadedResourceState', - data: null, - }); - - expect(isDeletionInProgress(state)).toBe(false); - }); - - it('returns false when resource state is failed', () => { - const state = createStateWithDeletionSubmissionResourceState({ - type: 'FailedResourceState', - error: createServerApiError('Not Found'), - }); - - expect(isDeletionInProgress(state)).toBe(false); - }); - }); - - describe('isDeletionSuccessful()', () => { - it('returns false when resource state is uninitialised', () => { - expect(isDeletionSuccessful(initialState)).toBe(false); - }); - - it('returns false when resource state is loading', () => { - const state = createStateWithDeletionSubmissionResourceState({ - type: 'LoadingResourceState', - previousState: { type: 'UninitialisedResourceState' }, - }); - - expect(isDeletionSuccessful(state)).toBe(false); - }); - - it('returns true when resource state is loaded', () => { - const state = createStateWithDeletionSubmissionResourceState({ - type: 'LoadedResourceState', - data: null, - }); - - expect(isDeletionSuccessful(state)).toBe(true); - }); - - it('returns false when resource state is failed', () => { - const state = createStateWithDeletionSubmissionResourceState({ - type: 'FailedResourceState', - error: createServerApiError('Not Found'), - }); - - expect(isDeletionSuccessful(state)).toBe(false); - }); - }); - - describe('getDeletionError()', () => { - it('returns undefined when resource state is uninitialised', () => { - expect(getDeletionError(initialState)).toBeUndefined(); - }); - - it('returns undefined when resource state is loading', () => { - const state = createStateWithDeletionSubmissionResourceState({ - type: 'LoadingResourceState', - previousState: { type: 'UninitialisedResourceState' }, - }); - - expect(getDeletionError(state)).toBeUndefined(); - }); - - it('returns undefined when resource state is loaded', () => { - const state = createStateWithDeletionSubmissionResourceState({ - type: 'LoadedResourceState', - data: null, - }); - - expect(getDeletionError(state)).toBeUndefined(); - }); - - it('returns error when resource state is failed', () => { - const state = createStateWithDeletionSubmissionResourceState({ - type: 'FailedResourceState', - error: createServerApiError('Not Found'), - }); - - expect(getDeletionError(state)).toStrictEqual(createServerApiError('Not Found')); - }); - }); - - describe('getDeletionSubmissionResourceState()', () => { - it('returns submission resource state', () => { - expect(getDeletionSubmissionResourceState(initialState)).toStrictEqual({ - type: 'UninitialisedResourceState', - }); - }); - }); - - describe('getDeletionDialogEntry()', () => { - it('returns undefined when no entry is set', () => { - expect(getDeletionDialogEntry(initialState)).toBeUndefined(); - }); - - it('returns entry when entry is set', () => { - const entry = createSampleTrustedApp(5); - const state = { ...initialState, deletionDialog: { ...initialState.deletionDialog, entry } }; - - expect(getDeletionDialogEntry(state)).toStrictEqual(entry); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts deleted file mode 100644 index 743d8b84760ba..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { createSelector } from 'reselect'; -import { ServerApiError } from '../../../../common/types'; -import { - Immutable, - NewTrustedApp, - PolicyData, - TrustedApp, -} from '../../../../../common/endpoint/types'; -import { MANAGEMENT_PAGE_SIZE_OPTIONS } from '../../../common/constants'; - -import { - AsyncResourceState, - getCurrentResourceError, - getLastLoadedResourceState, - isFailedResourceState, - isLoadedResourceState, - isLoadingResourceState, - isOutdatedResourceState, - LoadedResourceState, - Pagination, - TrustedAppsListData, - TrustedAppsListPageLocation, - TrustedAppsListPageState, -} from '../state'; -import { GetPolicyListResponse } from '../../policy/types'; - -export const needsRefreshOfListData = (state: Immutable): boolean => { - const freshDataTimestamp = state.listView.freshDataTimestamp; - const currentPage = state.listView.listResourceState; - const location = state.location; - const forceRefresh = state.forceRefresh; - return ( - Boolean(state.active) && - (forceRefresh || - isOutdatedResourceState(currentPage, (data) => { - return ( - data.pageIndex === location.page_index && - data.pageSize === location.page_size && - data.timestamp >= freshDataTimestamp - ); - })) - ); -}; - -export const getListResourceState = ( - state: Immutable -): Immutable> | undefined => { - return state.listView.listResourceState; -}; - -export const getLastLoadedListResourceState = ( - state: Immutable -): Immutable> | undefined => { - return getLastLoadedResourceState(state.listView.listResourceState); -}; - -export const getListItems = ( - state: Immutable -): Immutable => { - return getLastLoadedResourceState(state.listView.listResourceState)?.data.items || []; -}; - -export const getCurrentLocationPageIndex = (state: Immutable): number => { - return state.location.page_index; -}; - -export const getCurrentLocationPageSize = (state: Immutable): number => { - return state.location.page_size; -}; - -export const getCurrentLocationFilter = (state: Immutable): string => { - return state.location.filter; -}; - -export const getCurrentLocationIncludedPolicies = ( - state: Immutable -): string => { - return state.location.included_policies; -}; - -export const getListTotalItemsCount = (state: Immutable): number => { - return getLastLoadedResourceState(state.listView.listResourceState)?.data.totalItemsCount || 0; -}; - -export const getListPagination = (state: Immutable): Pagination => { - const lastLoadedResourceState = getLastLoadedResourceState(state.listView.listResourceState); - - return { - pageIndex: state.location.page_index, - pageSize: state.location.page_size, - totalItemCount: lastLoadedResourceState?.data.totalItemsCount || 0, - pageSizeOptions: [...MANAGEMENT_PAGE_SIZE_OPTIONS], - }; -}; - -export const getCurrentLocation = ( - state: Immutable -): TrustedAppsListPageLocation => state.location; - -export const getListErrorMessage = ( - state: Immutable -): string | undefined => { - return getCurrentResourceError(state.listView.listResourceState)?.message; -}; - -export const isListLoading = (state: Immutable): boolean => { - return isLoadingResourceState(state.listView.listResourceState); -}; - -export const isDeletionDialogOpen = (state: Immutable): boolean => { - return state.deletionDialog.entry !== undefined; -}; - -export const isDeletionInProgress = (state: Immutable): boolean => { - return isLoadingResourceState(state.deletionDialog.submissionResourceState); -}; - -export const isDeletionSuccessful = (state: Immutable): boolean => { - return isLoadedResourceState(state.deletionDialog.submissionResourceState); -}; - -export const getDeletionError = ( - state: Immutable -): Immutable | undefined => { - const submissionResourceState = state.deletionDialog.submissionResourceState; - - return isFailedResourceState(submissionResourceState) ? submissionResourceState.error : undefined; -}; - -export const getDeletionSubmissionResourceState = ( - state: Immutable -): AsyncResourceState => { - return state.deletionDialog.submissionResourceState; -}; - -export const getDeletionDialogEntry = ( - state: Immutable -): Immutable | undefined => { - return state.deletionDialog.entry; -}; - -export const isCreationDialogLocation = (state: Immutable): boolean => { - return !!state.location.show; -}; - -export const getCreationSubmissionResourceState = ( - state: Immutable -): Immutable> => { - return state.creationDialog.submissionResourceState; -}; - -export const getCreationDialogFormEntry = ( - state: Immutable -): Immutable | undefined => { - return state.creationDialog.formState?.entry; -}; - -export const isCreationDialogFormValid = (state: Immutable): boolean => { - return state.creationDialog.formState?.isValid || false; -}; - -export const isCreationInProgress = (state: Immutable): boolean => { - return isLoadingResourceState(state.creationDialog.submissionResourceState); -}; - -export const isCreationSuccessful = (state: Immutable): boolean => { - return isLoadedResourceState(state.creationDialog.submissionResourceState); -}; - -export const getCreationError = ( - state: Immutable -): Immutable | undefined => { - const submissionResourceState = state.creationDialog.submissionResourceState; - - return isFailedResourceState(submissionResourceState) ? submissionResourceState.error : undefined; -}; - -export const entriesExistState: ( - state: Immutable -) => Immutable = (state) => state.entriesExist; - -export const checkingIfEntriesExist: (state: Immutable) => boolean = - createSelector(entriesExistState, (doEntriesExists) => { - return !isLoadedResourceState(doEntriesExists); - }); - -export const entriesExist: (state: Immutable) => boolean = createSelector( - entriesExistState, - (doEntriesExists) => { - return isLoadedResourceState(doEntriesExists) && doEntriesExists.data; - } -); - -export const prevEntriesExist: (state: Immutable) => boolean = - createSelector(entriesExistState, (doEntriesExists) => { - return ( - isLoadingResourceState(doEntriesExists) && !!getLastLoadedResourceState(doEntriesExists)?.data - ); - }); - -export const trustedAppsListPageActive: (state: Immutable) => boolean = ( - state -) => state.active; - -export const policiesState = ( - state: Immutable -): Immutable => state.policies; - -export const loadingPolicies: (state: Immutable) => boolean = - createSelector(policiesState, (policies) => isLoadingResourceState(policies)); - -export const listOfPolicies: ( - state: Immutable -) => Immutable = createSelector(policiesState, (policies) => { - return isLoadedResourceState(policies) ? policies.data.items : []; -}); - -export const isLoadingListOfPolicies: (state: Immutable) => boolean = - createSelector(policiesState, (policies) => { - return isLoadingResourceState(policies); - }); - -export const getMapOfPoliciesById: ( - state: Immutable -) => Immutable>> = createSelector( - listOfPolicies, - (policies) => { - return policies.reduce>>((mapById, policy) => { - mapById[policy.id] = policy; - return mapById; - }, {}) as Immutable>>; - } -); - -export const isEdit: (state: Immutable) => boolean = createSelector( - getCurrentLocation, - ({ show }) => { - return show === 'edit'; - } -); - -export const editItemId: (state: Immutable) => string | undefined = - createSelector(getCurrentLocation, ({ id }) => { - return id; - }); - -export const editItemState: ( - state: Immutable -) => Immutable['creationDialog']['editItem'] = (state) => { - return state.creationDialog.editItem; -}; - -export const isFetchingEditTrustedAppItem: (state: Immutable) => boolean = - createSelector(editItemState, (editTrustedAppState) => { - return editTrustedAppState ? isLoadingResourceState(editTrustedAppState) : false; - }); - -export const editTrustedAppFetchError: ( - state: Immutable -) => ServerApiError | undefined = createSelector(editItemState, (itemForEditState) => { - return itemForEditState && getCurrentResourceError(itemForEditState); -}); - -export const editingTrustedApp: ( - state: Immutable -) => undefined | Immutable = createSelector(editItemState, (editTrustedAppState) => { - if (editTrustedAppState && isLoadedResourceState(editTrustedAppState)) { - return editTrustedAppState.data; - } -}); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/test_utils/index.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/test_utils/index.ts deleted file mode 100644 index 32e1867db567c..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/test_utils/index.ts +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { combineReducers, createStore } from 'redux'; -import { OperatingSystem } from '@kbn/securitysolution-utils'; -import { TrustedApp } from '../../../../../common/endpoint/types'; -import { RoutingAction } from '../../../../common/store/routing'; - -import { - MANAGEMENT_DEFAULT_PAGE, - MANAGEMENT_DEFAULT_PAGE_SIZE, - MANAGEMENT_PAGE_SIZE_OPTIONS, - MANAGEMENT_STORE_GLOBAL_NAMESPACE, - MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE, -} from '../../../common/constants'; - -import { - AsyncResourceState, - FailedResourceState, - LoadedResourceState, - LoadingResourceState, - Pagination, - StaleResourceState, - TrustedAppsListData, - TrustedAppsListPageState, - UninitialisedResourceState, -} from '../state'; - -import { trustedAppsPageReducer } from '../store/reducer'; -import { TrustedAppsListResourceStateChanged } from '../store/action'; - -const OPERATING_SYSTEMS: OperatingSystem[] = [ - OperatingSystem.WINDOWS, - OperatingSystem.MAC, - OperatingSystem.LINUX, -]; - -const generate = (count: number, generator: (i: number) => T) => - [...new Array(count).keys()].map(generator); - -export const createSampleTrustedApp = (i: number, longTexts?: boolean): TrustedApp => { - return { - id: String(i), - version: 'abc123', - name: generate(longTexts ? 10 : 1, () => `trusted app ${i}`).join(' '), - description: generate(longTexts ? 10 : 1, () => `Trusted App ${i}`).join(' '), - created_at: '1 minute ago', - created_by: 'someone', - updated_at: '1 minute ago', - updated_by: 'someone', - os: OPERATING_SYSTEMS[i % 3], - entries: [], - effectScope: { type: 'global' }, - }; -}; - -export const createSampleTrustedApps = ( - pagination: Partial, - longTexts?: boolean -): TrustedApp[] => { - const fullPagination = { ...createDefaultPagination(), ...pagination }; - - return generate(fullPagination.pageSize, (i: number) => createSampleTrustedApp(i, longTexts)); -}; - -export const createTrustedAppsListData = ( - pagination: Partial, - timestamp: number, - longTexts?: boolean -) => { - const fullPagination = { ...createDefaultPagination(), ...pagination }; - - return { - items: createSampleTrustedApps(fullPagination, longTexts), - pageSize: fullPagination.pageSize, - pageIndex: fullPagination.pageIndex, - totalItemsCount: fullPagination.totalItemCount, - timestamp, - filter: '', - includedPolicies: '', - }; -}; - -export const createServerApiError = (message: string) => ({ - statusCode: 500, - error: 'Internal Server Error', - message, -}); - -export const createUninitialisedResourceState = (): UninitialisedResourceState => ({ - type: 'UninitialisedResourceState', -}); - -export const createListLoadedResourceState = ( - pagination: Partial, - timestamp: number, - longTexts?: boolean -): LoadedResourceState => ({ - type: 'LoadedResourceState', - data: createTrustedAppsListData(pagination, timestamp, longTexts), -}); - -export const createListFailedResourceState = ( - message: string, - lastLoadedState?: LoadedResourceState -): FailedResourceState => ({ - type: 'FailedResourceState', - error: createServerApiError(message), - lastLoadedState, -}); - -export const createListLoadingResourceState = ( - previousState: StaleResourceState = createUninitialisedResourceState() -): LoadingResourceState => ({ - type: 'LoadingResourceState', - previousState, -}); - -export const createListComplexLoadingResourceState = ( - pagination: Partial, - timestamp: number -): LoadingResourceState => - createListLoadingResourceState( - createListFailedResourceState( - 'Internal Server Error', - createListLoadedResourceState(pagination, timestamp) - ) - ); - -export const createDefaultPagination = (): Pagination => ({ - pageIndex: MANAGEMENT_DEFAULT_PAGE, - pageSize: MANAGEMENT_DEFAULT_PAGE_SIZE, - totalItemCount: 200, - pageSizeOptions: [...MANAGEMENT_PAGE_SIZE_OPTIONS], -}); - -export const createLoadedListViewWithPagination = ( - freshDataTimestamp: number, - pagination: Partial = createDefaultPagination() -): TrustedAppsListPageState['listView'] => ({ - listResourceState: createListLoadedResourceState(pagination, freshDataTimestamp), - freshDataTimestamp, -}); - -export const createUserChangedUrlAction = (path: string, search: string = ''): RoutingAction => { - return { type: 'userChangedUrl', payload: { pathname: path, search, hash: '' } }; -}; - -export const createTrustedAppsListResourceStateChangedAction = ( - newState: AsyncResourceState -): TrustedAppsListResourceStateChanged => ({ - type: 'trustedAppsListResourceStateChanged', - payload: { newState }, -}); - -export const createGlobalNoMiddlewareStore = () => { - return createStore( - combineReducers({ - [MANAGEMENT_STORE_GLOBAL_NAMESPACE]: combineReducers({ - [MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE]: trustedAppsPageReducer, - }), - }) - ); -}; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_app_deletion_dialog.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_app_deletion_dialog.test.tsx.snap deleted file mode 100644 index 070f1b9eabe23..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_app_deletion_dialog.test.tsx.snap +++ /dev/null @@ -1,414 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`TrustedAppDeletionDialog renders correctly initially 1`] = ` - -
- -`; - -exports[`TrustedAppDeletionDialog renders correctly when deletion failed 1`] = ` - -
-
-
-
- -
-
-
- Delete " - - trusted app 3 - - " -
-
-
-
-
-
-
-
-
-

- Deleting this entry will remove it from all associated policies. -

-
-
-
-
-
-

- This action cannot be undone. Are you sure you wish to continue? -

-
-
-
-
- - -
-
-
-
-
- -`; - -exports[`TrustedAppDeletionDialog renders correctly when deletion is in progress 1`] = ` - -
-
-
-
- -
-
-
- Delete " - - trusted app 3 - - " -
-
-
-
-
-
-
-
-
-

- Deleting this entry will remove it from all associated policies. -

-
-
-
-
-
-

- This action cannot be undone. Are you sure you wish to continue? -

-
-
-
-
- - -
-
-
-
-
- -`; - -exports[`TrustedAppDeletionDialog renders correctly when dialog started 1`] = ` - -
-
-
-
- -
-
-
- Delete " - - trusted app 3 - - " -
-
-
-
-
-
-
-
-
-

- Deleting this entry will remove it from all associated policies. -

-
-
-
-
-
-

- This action cannot be undone. Are you sure you wish to continue? -

-
-
-
-
- - -
-
-
-
-
- -`; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_flyout.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_flyout.tsx deleted file mode 100644 index f76ac89474e7b..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_flyout.tsx +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - EuiButton, - EuiButtonEmpty, - EuiCallOut, - EuiFlexGroup, - EuiFlexItem, - EuiFlyout, - EuiFlyoutBody, - EuiFlyoutFooter, - EuiFlyoutHeader, - EuiLink, - EuiSpacer, - EuiText, - EuiTitle, -} from '@elastic/eui'; -import React, { memo, useCallback, useEffect, useState, useMemo } from 'react'; -import { EuiFlyoutProps } from '@elastic/eui/src/components/flyout/flyout'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { useDispatch } from 'react-redux'; -import { useHistory } from 'react-router-dom'; -import { i18n } from '@kbn/i18n'; -import _ from 'lodash'; -import { CreateTrustedAppForm, CreateTrustedAppFormProps } from './create_trusted_app_form'; -import { - editTrustedAppFetchError, - getCreationDialogFormEntry, - getCreationError, - getCurrentLocation, - isCreationDialogFormValid, - isCreationInProgress, - isCreationSuccessful, - isEdit, - listOfPolicies, - loadingPolicies, -} from '../../store/selectors'; -import { AppAction } from '../../../../../common/store/actions'; -import { useTrustedAppsSelector } from '../hooks'; - -import { ABOUT_TRUSTED_APPS } from '../translations'; -import { defaultNewTrustedApp } from '../../store/builders'; -import { getTrustedAppsListPath } from '../../../../common/routing'; -import { useKibana, useToasts } from '../../../../../common/lib/kibana'; -import { useTestIdGenerator } from '../../../../components/hooks/use_test_id_generator'; -import { useLicense } from '../../../../../common/hooks/use_license'; -import { isGlobalEffectScope } from '../../state/type_guards'; -import { NewTrustedApp } from '../../../../../../common/endpoint/types'; - -export type CreateTrustedAppFlyoutProps = Omit; -export const CreateTrustedAppFlyout = memo( - ({ onClose, ...flyoutProps }) => { - const dispatch = useDispatch<(action: AppAction) => void>(); - const history = useHistory(); - const toasts = useToasts(); - - const creationInProgress = useTrustedAppsSelector(isCreationInProgress); - const creationErrors = useTrustedAppsSelector(getCreationError); - const creationSuccessful = useTrustedAppsSelector(isCreationSuccessful); - const isFormValid = useTrustedAppsSelector(isCreationDialogFormValid); - const isLoadingPolicies = useTrustedAppsSelector(loadingPolicies); - const policyList = useTrustedAppsSelector(listOfPolicies); - const isEditMode = useTrustedAppsSelector(isEdit); - const trustedAppFetchError = useTrustedAppsSelector(editTrustedAppFetchError); - const formValues = useTrustedAppsSelector(getCreationDialogFormEntry) || defaultNewTrustedApp(); - const location = useTrustedAppsSelector(getCurrentLocation); - const isPlatinumPlus = useLicense().isPlatinumPlus(); - const docLinks = useKibana().services.docLinks; - const [isFormDirty, setIsFormDirty] = useState(false); - - const dataTestSubj = flyoutProps['data-test-subj']; - - const policies = useMemo(() => { - return { - // Casting is needed due to the use of `Immutable<>` on the return value from the selector above - options: policyList as CreateTrustedAppFormProps['policies']['options'], - isLoading: isLoadingPolicies, - }; - }, [isLoadingPolicies, policyList]); - - const creationErrorsMessage = useMemo(() => { - return creationErrors?.message ?? []; - }, [creationErrors]); - - const getTestId = useTestIdGenerator(dataTestSubj); - - const handleCancelClick = useCallback(() => { - if (creationInProgress) { - return; - } - onClose(); - }, [onClose, creationInProgress]); - - const handleSaveClick = useCallback( - () => dispatch({ type: 'trustedAppCreationDialogConfirmed' }), - [dispatch] - ); - - const handleFormOnChange = useCallback( - (newFormState) => { - dispatch({ - type: 'trustedAppCreationDialogFormStateUpdated', - payload: { entry: newFormState.item, isValid: newFormState.isValid }, - }); - if (_.isEqual(formValues, newFormState.item) === false) { - setIsFormDirty(true); - } - }, - - [dispatch, formValues] - ); - - const [wasByPolicy, setWasByPolicy] = useState(!isGlobalEffectScope(formValues.effectScope)); - // set initial state of `wasByPolicy` that checks if the initial state of the exception was by policy or not - useEffect(() => { - if (!isFormDirty && formValues.effectScope) { - setWasByPolicy(!isGlobalEffectScope(formValues.effectScope)); - } - }, [isFormDirty, formValues.effectScope]); - - const isGlobal = useMemo(() => { - return isGlobalEffectScope((formValues as NewTrustedApp).effectScope); - }, [formValues]); - - const showExpiredLicenseBanner = useMemo(() => { - return !isPlatinumPlus && isEditMode && wasByPolicy && (!isGlobal || isFormDirty); - }, [isPlatinumPlus, isEditMode, isGlobal, isFormDirty, wasByPolicy]); - - // If there was a failure trying to retrieve the Trusted App for edit item, - // then redirect back to the list ++ show toast message. - useEffect(() => { - if (trustedAppFetchError) { - // Replace the current URL route so that user does not keep hitting this page via browser back/fwd buttons - history.replace( - getTrustedAppsListPath({ - ...location, - show: undefined, - id: undefined, - }) - ); - - toasts.addWarning( - i18n.translate( - 'xpack.securitySolution.trustedapps.createTrustedAppFlyout.notFoundToastMessage', - { - defaultMessage: 'Unable to edit trusted application ({apiMsg})', - values: { - apiMsg: trustedAppFetchError.message, - }, - } - ) - ); - } - }, [history, location, toasts, trustedAppFetchError]); - - // If it was created, then close flyout - useEffect(() => { - if (creationSuccessful) { - onClose(); - } - }, [onClose, creationSuccessful]); - - return ( - - - -

- {isEditMode ? ( - - ) : ( - - )} -

-
-
- {showExpiredLicenseBanner && ( - - - - - - - )} - - -

- {i18n.translate('xpack.securitySolution.trustedApps.detailsSectionTitle', { - defaultMessage: 'Details', - })} -

-
- - {!isEditMode && ( - <> - -

{ABOUT_TRUSTED_APPS}

-
- - - )} - -
- - - - - - - - - - - {isEditMode ? ( - - ) : ( - - )} - - - - -
- ); - } -); - -CreateTrustedAppFlyout.displayName = 'NewTrustedAppFlyout'; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.tsx deleted file mode 100644 index 8e7246962b5ee..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.tsx +++ /dev/null @@ -1,623 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { ChangeEventHandler, memo, useCallback, useEffect, useMemo, useState } from 'react'; -import { - EuiFieldText, - EuiForm, - EuiFormRow, - EuiHorizontalRule, - EuiSuperSelect, - EuiSuperSelectOption, - EuiTextArea, - EuiText, - EuiSpacer, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { - hasSimpleExecutableName, - isPathValid, - ConditionEntryField, - OperatingSystem, -} from '@kbn/securitysolution-utils'; -import { EuiFormProps } from '@elastic/eui/src/components/form/form'; - -import { - TrustedAppConditionEntry, - EffectScope, - MacosLinuxConditionEntry, - MaybeImmutable, - NewTrustedApp, -} from '../../../../../../common/endpoint/types'; -import { - isValidHash, - getDuplicateFields, -} from '../../../../../../common/endpoint/service/trusted_apps/validations'; - -import { - isGlobalEffectScope, - isMacosLinuxTrustedAppCondition, - isPolicyEffectScope, - isWindowsTrustedAppCondition, -} from '../../state/type_guards'; -import { defaultConditionEntry } from '../../store/builders'; -import { CONDITION_FIELD_TITLE, OS_TITLES } from '../translations'; -import { LogicalConditionBuilder, LogicalConditionBuilderProps } from './logical_condition'; -import { useTestIdGenerator } from '../../../../components/hooks/use_test_id_generator'; -import { useLicense } from '../../../../../common/hooks/use_license'; -import { - EffectedPolicySelect, - EffectedPolicySelection, - EffectedPolicySelectProps, -} from '../../../../components/effected_policy_select'; - -const OPERATING_SYSTEMS: readonly OperatingSystem[] = [ - OperatingSystem.MAC, - OperatingSystem.WINDOWS, - OperatingSystem.LINUX, -]; - -interface FieldValidationState { - /** If this fields state is invalid. Drives display of errors on the UI */ - isInvalid: boolean; - errors: React.ReactNode[]; - warnings: React.ReactNode[]; -} -interface ValidationResult { - /** Overall indicator if form is valid */ - isValid: boolean; - - /** Individual form field validations */ - result: Partial<{ - [key in keyof NewTrustedApp]: FieldValidationState; - }>; -} - -const addResultToValidation = ( - validation: ValidationResult, - field: keyof NewTrustedApp, - type: 'warnings' | 'errors', - resultValue: React.ReactNode -) => { - if (!validation.result[field]) { - validation.result[field] = { - isInvalid: false, - errors: [], - warnings: [], - }; - } - const errorMarkup: React.ReactNode = type === 'warnings' ?
{resultValue}
: resultValue; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - validation.result[field]![type].push(errorMarkup); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - validation.result[field]!.isInvalid = true; -}; - -const validateFormValues = (values: MaybeImmutable): ValidationResult => { - let isValid: ValidationResult['isValid'] = true; - const validation: ValidationResult = { - isValid, - result: {}, - }; - - // Name field - if (!values.name.trim()) { - isValid = false; - addResultToValidation( - validation, - 'name', - 'errors', - i18n.translate('xpack.securitySolution.trustedapps.create.nameRequiredMsg', { - defaultMessage: 'Name is required', - }) - ); - } - - if (!values.os) { - isValid = false; - addResultToValidation( - validation, - 'os', - 'errors', - i18n.translate('xpack.securitySolution.trustedapps.create.osRequiredMsg', { - defaultMessage: 'Operating System is required', - }) - ); - } - - if (!values.entries.length) { - isValid = false; - addResultToValidation( - validation, - 'entries', - 'errors', - i18n.translate('xpack.securitySolution.trustedapps.create.conditionRequiredMsg', { - defaultMessage: 'At least one Field definition is required', - }) - ); - } else { - const duplicated = getDuplicateFields(values.entries as TrustedAppConditionEntry[]); - if (duplicated.length) { - isValid = false; - duplicated.forEach((field) => { - addResultToValidation( - validation, - 'entries', - 'errors', - i18n.translate('xpack.securitySolution.trustedapps.create.conditionFieldDuplicatedMsg', { - defaultMessage: '{field} cannot be added more than once', - values: { field: CONDITION_FIELD_TITLE[field] }, - }) - ); - }); - } - values.entries.forEach((entry, index) => { - const isValidPathEntry = isPathValid({ - os: values.os, - field: entry.field, - type: entry.type, - value: entry.value, - }); - - if (!entry.field || !entry.value.trim()) { - isValid = false; - addResultToValidation( - validation, - 'entries', - 'errors', - i18n.translate( - 'xpack.securitySolution.trustedapps.create.conditionFieldValueRequiredMsg', - { - defaultMessage: '[{row}] Field entry must have a value', - values: { row: index + 1 }, - } - ) - ); - } else if (entry.field === ConditionEntryField.HASH && !isValidHash(entry.value)) { - isValid = false; - addResultToValidation( - validation, - 'entries', - 'errors', - i18n.translate('xpack.securitySolution.trustedapps.create.conditionFieldInvalidHashMsg', { - defaultMessage: '[{row}] Invalid hash value', - values: { row: index + 1 }, - }) - ); - } else if (!isValidPathEntry) { - addResultToValidation( - validation, - 'entries', - 'warnings', - i18n.translate('xpack.securitySolution.trustedapps.create.conditionFieldInvalidPathMsg', { - defaultMessage: '[{row}] Path may be formed incorrectly; verify value', - values: { row: index + 1 }, - }) - ); - } else if ( - isValidPathEntry && - !hasSimpleExecutableName({ os: values.os, value: entry.value, type: entry.type }) - ) { - addResultToValidation( - validation, - 'entries', - 'warnings', - i18n.translate( - 'xpack.securitySolution.trustedapps.create.conditionFieldDegradedPerformanceMsg', - { - defaultMessage: `[{row}] A wildcard in the filename will affect the endpoint's performance`, - values: { row: index + 1 }, - } - ) - ); - } - }); - } - - validation.isValid = isValid; - return validation; -}; - -export interface TrustedAppFormState { - isValid: boolean; - item: NewTrustedApp; -} - -export type CreateTrustedAppFormProps = Pick< - EuiFormProps, - 'className' | 'data-test-subj' | 'isInvalid' | 'error' | 'invalidCallout' -> & { - /** The trusted app values that will be passed to the form */ - trustedApp: MaybeImmutable; - isEditMode: boolean; - isDirty: boolean; - wasByPolicy: boolean; - onChange: (state: TrustedAppFormState) => void; - /** Setting passed on to the EffectedPolicySelect component */ - policies: Pick; - /** if form should be shown full width of parent container */ - fullWidth?: boolean; -}; -export const CreateTrustedAppForm = memo( - ({ - fullWidth, - isEditMode, - isDirty, - wasByPolicy, - onChange, - trustedApp: _trustedApp, - policies = { options: [] }, - ...formProps - }) => { - const trustedApp = _trustedApp as NewTrustedApp; - - const dataTestSubj = formProps['data-test-subj']; - - const isPlatinumPlus = useLicense().isPlatinumPlus(); - - const isGlobal = useMemo(() => { - return isGlobalEffectScope(trustedApp.effectScope); - }, [trustedApp]); - - const showAssignmentSection = useMemo(() => { - return isPlatinumPlus || (isEditMode && (!isGlobal || (wasByPolicy && isGlobal && isDirty))); - }, [isEditMode, isGlobal, isDirty, isPlatinumPlus, wasByPolicy]); - - const osOptions: Array> = useMemo( - () => OPERATING_SYSTEMS.map((os) => ({ value: os, inputDisplay: OS_TITLES[os] })), - [] - ); - - // We create local state for the list of policies because we want the selected policies to - // persist while the user is on the form and possibly toggling between global/non-global - const [selectedPolicies, setSelectedPolicies] = useState({ - isGlobal, - selected: [], - }); - - const [validationResult, setValidationResult] = useState(() => - validateFormValues(trustedApp) - ); - - const [wasVisited, setWasVisited] = useState< - Partial<{ - [key in keyof NewTrustedApp]: boolean; - }> - >({}); - - const getTestId = useTestIdGenerator(dataTestSubj); - - const notifyOfChange = useCallback( - (updatedFormValues: TrustedAppFormState['item']) => { - const updatedValidationResult = validateFormValues(updatedFormValues); - - setValidationResult(updatedValidationResult); - - onChange({ - item: updatedFormValues, - isValid: updatedValidationResult.isValid, - }); - }, - [onChange] - ); - - const handleAndClick = useCallback(() => { - if (trustedApp.os === OperatingSystem.WINDOWS) { - notifyOfChange({ - ...trustedApp, - entries: [...trustedApp.entries, defaultConditionEntry()].filter( - isWindowsTrustedAppCondition - ), - }); - } else { - notifyOfChange({ - ...trustedApp, - entries: [ - ...trustedApp.entries.filter(isMacosLinuxTrustedAppCondition), - defaultConditionEntry(), - ], - }); - } - }, [notifyOfChange, trustedApp]); - - const handleDomChangeEvents = useCallback< - ChangeEventHandler - >( - ({ target: { name, value } }) => { - notifyOfChange({ - ...trustedApp, - [name]: value, - }); - }, - [notifyOfChange, trustedApp] - ); - - // Handles keeping track if an input form field has been visited - const handleDomBlurEvents = useCallback>( - ({ target: { name } }) => { - setWasVisited((prevState) => { - return { - ...prevState, - [name]: true, - }; - }); - }, - [] - ); - - const handleOsChange = useCallback<(v: OperatingSystem) => void>( - (newOsValue) => { - setWasVisited((prevState) => { - return { - ...prevState, - os: true, - }; - }); - - const updatedState: NewTrustedApp = { - ...trustedApp, - entries: [], - os: newOsValue, - }; - if (updatedState.os !== OperatingSystem.WINDOWS) { - updatedState.entries.push( - ...(trustedApp.entries.filter((entry) => - isMacosLinuxTrustedAppCondition(entry) - ) as MacosLinuxConditionEntry[]) - ); - if (updatedState.entries.length === 0) { - updatedState.entries.push(defaultConditionEntry()); - } - } else { - updatedState.entries.push(...trustedApp.entries); - } - - notifyOfChange(updatedState); - }, - [notifyOfChange, trustedApp] - ); - - const handleEntryRemove = useCallback( - (entry: NewTrustedApp['entries'][0]) => { - notifyOfChange({ - ...trustedApp, - entries: trustedApp.entries.filter((item) => item !== entry), - } as NewTrustedApp); - }, - [notifyOfChange, trustedApp] - ); - - const handleEntryChange = useCallback( - (newEntry, oldEntry) => { - if (trustedApp.os === OperatingSystem.WINDOWS) { - notifyOfChange({ - ...trustedApp, - entries: trustedApp.entries.map((item) => { - if (item === oldEntry) { - return newEntry; - } - return item; - }), - } as NewTrustedApp); - } else { - notifyOfChange({ - ...trustedApp, - entries: trustedApp.entries.map((item) => { - if (item === oldEntry) { - return newEntry; - } - return item; - }), - } as NewTrustedApp); - } - }, - [notifyOfChange, trustedApp] - ); - - const handleConditionBuilderOnVisited: LogicalConditionBuilderProps['onVisited'] = - useCallback(() => { - setWasVisited((prevState) => { - return { - ...prevState, - entries: true, - }; - }); - }, []); - - const handlePolicySelectChange: EffectedPolicySelectProps['onChange'] = useCallback( - (selection) => { - setSelectedPolicies(() => selection); - - let newEffectedScope: EffectScope; - - if (selection.isGlobal) { - newEffectedScope = { - type: 'global', - }; - } else { - newEffectedScope = { - type: 'policy', - policies: selection.selected.map((policy) => policy.id), - }; - } - - notifyOfChange({ - ...trustedApp, - effectScope: newEffectedScope, - }); - }, - [notifyOfChange, trustedApp] - ); - - // Anytime the form values change, re-validate - useEffect(() => { - setValidationResult((prevState) => { - const newResults = validateFormValues(trustedApp); - - // Only notify if the overall validation result is different - if (newResults.isValid !== prevState.isValid) { - notifyOfChange(trustedApp); - } - - return newResults; - }); - }, [notifyOfChange, trustedApp]); - - // Anytime the TrustedApp has an effective scope of `policies`, then ensure that - // those polices are selected in the UI while at the same time preserving prior - // selections (UX requirement) - useEffect(() => { - setSelectedPolicies((currentSelection) => { - if (isPolicyEffectScope(trustedApp.effectScope) && policies.options.length > 0) { - const missingSelectedPolicies: EffectedPolicySelectProps['selected'] = []; - - for (const policyId of trustedApp.effectScope.policies) { - if ( - !currentSelection.selected.find( - (currentlySelectedPolicyItem) => currentlySelectedPolicyItem.id === policyId - ) - ) { - const newSelectedPolicy = policies.options.find((policy) => policy.id === policyId); - if (newSelectedPolicy) { - missingSelectedPolicies.push(newSelectedPolicy); - } - } - } - - if (missingSelectedPolicies.length) { - return { - ...currentSelection, - selected: [...currentSelection.selected, ...missingSelectedPolicies], - }; - } - } - - return currentSelection; - }); - }, [policies.options, trustedApp.effectScope]); - - return ( - - - - - - - - - -

- {i18n.translate('xpack.securitySolution.trustedApps.conditionsSectionTitle', { - defaultMessage: 'Conditions', - })} -

-
- - -

- {i18n.translate('xpack.securitySolution.trustedApps.conditionsSectionDescription', { - defaultMessage: - 'Select an operating system and add conditions. Availability of conditions may depend on your chosen OS.', - })} -

-
- - - - - - - - {showAssignmentSection ? ( - <> - - - - - - ) : null} -
- ); - } -); - -CreateTrustedAppForm.displayName = 'NewTrustedAppForm'; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/form.test.tsx similarity index 52% rename from x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.test.tsx rename to x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/form.test.tsx index 68dd43fa41152..dca86557f6309 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/form.test.tsx @@ -6,25 +6,30 @@ */ import React from 'react'; -import * as reactTestingLibrary from '@testing-library/react'; -import { fireEvent, getByTestId } from '@testing-library/dom'; +import { screen, cleanup, act, fireEvent, getByTestId } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { + TrustedAppEntryTypes, + OperatingSystem, + ConditionEntryField, +} from '@kbn/securitysolution-utils'; +import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '@kbn/securitysolution-list-constants'; -import { ConditionEntryField, OperatingSystem } from '@kbn/securitysolution-utils'; -import { NewTrustedApp } from '../../../../../../common/endpoint/types'; +import { TrustedAppsForm } from './form'; +import { + ArtifactFormComponentOnChangeCallbackProps, + ArtifactFormComponentProps, +} from '../../../../components/artifact_list_page'; import { AppContextTestRender, createAppRootMockRenderer, } from '../../../../../common/mock/endpoint'; - -import { CreateTrustedAppForm, CreateTrustedAppFormProps } from './create_trusted_app_form'; -import { defaultNewTrustedApp } from '../../store/builders'; -import { EndpointDocGenerator } from '../../../../../../common/endpoint/generate_data'; -import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; +import { INPUT_ERRORS } from '../translations'; import { licenseService } from '../../../../../common/hooks/use_license'; import { forceHTMLElementOffsetWidth } from '../../../../components/effected_policy_select/test_utils'; +import type { PolicyData, TrustedAppConditionEntry } from '../../../../../../common/endpoint/types'; -jest.mock('../../../../../common/hooks/use_experimental_features'); -const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock; +import { EndpointDocGenerator } from '../../../../../../common/endpoint/generate_data'; jest.mock('../../../../../common/hooks/use_license', () => { const licenseServiceInstance = { @@ -38,55 +43,108 @@ jest.mock('../../../../../common/hooks/use_license', () => { }; }); -describe('When using the Trusted App Form', () => { - const dataTestSubjForForm = 'createForm'; +describe('Trusted apps form', () => { + const formPrefix = 'trustedApps-form'; const generator = new EndpointDocGenerator('effected-policy-select'); - let resetHTMLElementOffsetWidth: ReturnType; + let formProps: jest.Mocked; let mockedContext: AppContextTestRender; - let formProps: jest.Mocked; let renderResult: ReturnType; + let latestUpdatedItem: ArtifactFormComponentProps['item']; - // As the form's `onChange()` callback is executed, this variable will - // hold the latest updated trusted app. Use it to re-render - let latestUpdatedTrustedApp: NewTrustedApp; - - const getUI = () => ; + const getUI = () => ; const render = () => { return (renderResult = mockedContext.render(getUI())); }; const rerender = () => renderResult.rerender(getUI()); - const rerenderWithLatestTrustedApp = () => { - formProps.trustedApp = latestUpdatedTrustedApp; + const rerenderWithLatestProps = () => { + formProps.item = latestUpdatedItem; rerender(); }; + function createEntry( + field: T, + type: TrustedAppEntryTypes, + value: string + ): TrustedAppConditionEntry { + return { + field, + type, + operator: 'included', + value, + }; + } + + function createItem( + overrides: Partial = {} + ): ArtifactFormComponentProps['item'] { + const defaults: ArtifactFormComponentProps['item'] = { + list_id: ENDPOINT_TRUSTED_APPS_LIST_ID, + name: '', + description: '', + os_types: [OperatingSystem.WINDOWS], + entries: [createEntry(ConditionEntryField.HASH, 'match', '')], + type: 'simple', + tags: ['policy:all'], + }; + return { + ...defaults, + ...overrides, + }; + } + + function createOnChangeArgs( + overrides: Partial + ): ArtifactFormComponentOnChangeCallbackProps { + const defaults = { + item: createItem(), + isValid: false, + }; + return { + ...defaults, + ...overrides, + }; + } + + function createPolicies(): PolicyData[] { + const policies = [ + generator.generatePolicyPackagePolicy(), + generator.generatePolicyPackagePolicy(), + ]; + policies.map((p, i) => { + p.id = `id-${i}`; + p.name = `some-policy-${Math.random().toString(36).split('.').pop()}`; + return p; + }); + return policies; + } + // Some helpers const setTextFieldValue = (textField: HTMLInputElement | HTMLTextAreaElement, value: string) => { - reactTestingLibrary.act(() => { + act(() => { fireEvent.change(textField, { target: { value }, }); fireEvent.blur(textField); }); }; - const getNameField = (dataTestSub: string = dataTestSubjForForm): HTMLInputElement => { + const getDetailsBlurb = (dataTestSub: string = formPrefix): HTMLInputElement => { + return renderResult.queryByTestId(`${dataTestSub}-about`) as HTMLInputElement; + }; + const getNameField = (dataTestSub: string = formPrefix): HTMLInputElement => { return renderResult.getByTestId(`${dataTestSub}-nameTextField`) as HTMLInputElement; }; - const getOsField = (dataTestSub: string = dataTestSubjForForm): HTMLButtonElement => { + const getOsField = (dataTestSub: string = formPrefix): HTMLButtonElement => { return renderResult.getByTestId(`${dataTestSub}-osSelectField`) as HTMLButtonElement; }; - const getDescriptionField = (dataTestSub: string = dataTestSubjForForm): HTMLTextAreaElement => { + const getDescriptionField = (dataTestSub: string = formPrefix): HTMLTextAreaElement => { return renderResult.getByTestId(`${dataTestSub}-descriptionField`) as HTMLTextAreaElement; }; - const getCondition = ( - index: number = 0, - dataTestSub: string = dataTestSubjForForm - ): HTMLElement => { + const getCondition = (index: number = 0, dataTestSub: string = formPrefix): HTMLElement => { return renderResult.getByTestId(`${dataTestSub}-conditionsBuilder-group1-entry${index}`); }; - const getAllConditions = (dataTestSub: string = dataTestSubjForForm): HTMLElement[] => { + const getAllConditions = (dataTestSub: string = formPrefix): HTMLElement[] => { return Array.from( renderResult.getByTestId(`${dataTestSub}-conditionsBuilder-group1-entries`).children ) as HTMLElement[]; @@ -97,19 +155,16 @@ describe('When using the Trusted App Form', () => { const getConditionFieldSelect = (condition: HTMLElement): HTMLButtonElement => { return getByTestId(condition, `${condition.dataset.testSubj}-field`) as HTMLButtonElement; }; + const getConditionValue = (condition: HTMLElement): HTMLInputElement => { return getByTestId(condition, `${condition.dataset.testSubj}-value`) as HTMLInputElement; }; - const getConditionBuilderAndButton = ( - dataTestSub: string = dataTestSubjForForm - ): HTMLButtonElement => { + const getConditionBuilderAndButton = (dataTestSub: string = formPrefix): HTMLButtonElement => { return renderResult.getByTestId( `${dataTestSub}-conditionsBuilder-group1-AndButton` ) as HTMLButtonElement; }; - const getConditionBuilderAndConnectorBadge = ( - dataTestSub: string = dataTestSubjForForm - ): HTMLElement => { + const getConditionBuilderAndConnectorBadge = (dataTestSub: string = formPrefix): HTMLElement => { return renderResult.getByTestId(`${dataTestSub}-conditionsBuilder-group1-andConnector`); }; const getAllValidationErrors = (): HTMLElement[] => { @@ -121,42 +176,44 @@ describe('When using the Trusted App Form', () => { beforeEach(() => { resetHTMLElementOffsetWidth = forceHTMLElementOffsetWidth(); - useIsExperimentalFeatureEnabledMock.mockReturnValue(true); (licenseService.isPlatinumPlus as jest.Mock).mockReturnValue(true); - mockedContext = createAppRootMockRenderer(); - - latestUpdatedTrustedApp = defaultNewTrustedApp(); + latestUpdatedItem = createItem(); formProps = { - 'data-test-subj': dataTestSubjForForm, - trustedApp: latestUpdatedTrustedApp, - isEditMode: false, - isDirty: false, - wasByPolicy: false, + item: latestUpdatedItem, + mode: 'create', + disabled: false, + error: undefined, + policiesIsLoading: false, onChange: jest.fn((updates) => { - latestUpdatedTrustedApp = updates.item; + latestUpdatedItem = updates.item; }), - policies: { - options: [], - }, + policies: [], }; }); afterEach(() => { resetHTMLElementOffsetWidth(); - reactTestingLibrary.cleanup(); + cleanup(); }); - describe('and the form is rendered', () => { + describe('Details and Conditions', () => { beforeEach(() => render()); - it('should show Name as required after blur', () => { - expect(getNameField().required).toBe(false); - reactTestingLibrary.act(() => { - fireEvent.blur(getNameField()); - }); - expect(getNameField().required).toBe(true); + it('should NOT initially show any inline validation errors', () => { + expect(renderResult.container.querySelectorAll('.euiFormErrorText').length).toBe(0); + }); + + it('should hide details text when in edit mode', () => { + formProps.mode = 'edit'; + rerenderWithLatestProps(); + expect(getDetailsBlurb()).toBeNull(); + }); + + it('should show name required name blur', () => { + setTextFieldValue(getNameField(), ' '); + expect(renderResult.getByText(INPUT_ERRORS.name)).toBeTruthy(); }); it('should default OS to Windows', () => { @@ -165,42 +222,68 @@ describe('When using the Trusted App Form', () => { it('should allow user to select between 3 OSs', () => { const osField = getOsField(); - reactTestingLibrary.act(() => { - fireEvent.click(osField, { button: 1 }); - }); + userEvent.click(osField, { button: 1 }); const options = Array.from( renderResult.baseElement.querySelectorAll( '.euiSuperSelect__listbox button.euiSuperSelect__item' ) ).map((button) => button.textContent); - expect(options).toEqual(['Mac', 'Windows', 'Linux']); + expect(options).toEqual(['Linux', 'Mac', 'Windows']); }); it('should show Description as optional', () => { expect(getDescriptionField().required).toBe(false); }); - it('should NOT initially show any inline validation errors', () => { - expect(renderResult.container.querySelectorAll('.euiFormErrorText').length).toBe(0); + it('should be invalid if no name', () => { + const emptyName = ' '; + setTextFieldValue(getNameField(), emptyName); + const expected = createOnChangeArgs({ + item: createItem({ + name: emptyName, + }), + }); + expect(formProps.onChange).toHaveBeenCalledWith(expected); }); - it('should show top-level Errors', () => { - formProps.isInvalid = true; - formProps.error = 'a top level error'; - rerender(); - expect(renderResult.queryByText(formProps.error as string)).not.toBeNull(); + it('should correctly edit name', () => { + setTextFieldValue(getNameField(), 'z'); + const expected = createOnChangeArgs({ + item: createItem({ + name: 'z', + }), + }); + expect(formProps.onChange).toHaveBeenCalledWith(expected); + }); + + it('should correctly edit description', () => { + setTextFieldValue(getDescriptionField(), 'describe ta'); + const expected = createOnChangeArgs({ + item: createItem({ description: 'describe ta' }), + }); + expect(formProps.onChange).toHaveBeenCalledWith(expected); + }); + + it('should correctly change OS', () => { + userEvent.click(getOsField()); + userEvent.click(screen.getByRole('option', { name: 'Linux' })); + const expected = createOnChangeArgs({ + item: createItem({ os_types: [OperatingSystem.LINUX] }), + }); + expect(formProps.onChange).toHaveBeenCalledWith(expected); }); }); - describe('the condition builder component', () => { + describe('ConditionBuilder', () => { beforeEach(() => render()); - it('should show an initial condition entry with labels', () => { + it('should default to hash entry field', () => { const defaultCondition = getCondition(); const labels = Array.from(defaultCondition.querySelectorAll('.euiFormRow__labelWrapper')).map( (label) => (label.textContent || '').trim() ); expect(labels).toEqual(['Field', 'Operator', 'Value', '']); + expect(formProps.onChange).not.toHaveBeenCalled(); }); it('should not allow the entry to be removed if its the only one displayed', () => { @@ -210,9 +293,7 @@ describe('When using the Trusted App Form', () => { it('should display 3 options for Field for Windows', () => { const conditionFieldSelect = getConditionFieldSelect(getCondition()); - reactTestingLibrary.act(() => { - fireEvent.click(conditionFieldSelect, { button: 1 }); - }); + userEvent.click(conditionFieldSelect, { button: 1 }); const options = Array.from( renderResult.baseElement.querySelectorAll( '.euiSuperSelect__listbox button.euiSuperSelect__item' @@ -228,12 +309,37 @@ describe('When using the Trusted App Form', () => { it('should show the value field as required after blur', () => { expect(getConditionValue(getCondition()).required).toEqual(false); - reactTestingLibrary.act(() => { + act(() => { fireEvent.blur(getConditionValue(getCondition())); }); expect(getConditionValue(getCondition()).required).toEqual(true); }); + it('should show path malformed warning', () => { + render(); + expect(screen.queryByText(INPUT_ERRORS.pathWarning(0))).toBeNull(); + + const propsItem: Partial = { + entries: [createEntry(ConditionEntryField.PATH, 'match', 'malformed-path')], + }; + formProps.item = { ...formProps.item, ...propsItem }; + render(); + expect(screen.getByText(INPUT_ERRORS.pathWarning(0))).not.toBeNull(); + }); + + it('should show wildcard in path warning', () => { + render(); + expect(screen.queryByText(INPUT_ERRORS.wildcardPathWarning(0))).toBeNull(); + + const propsItem: Partial = { + os_types: [OperatingSystem.LINUX], + entries: [createEntry(ConditionEntryField.PATH, 'wildcard', '/sys/wil*/*.app')], + }; + formProps.item = { ...formProps.item, ...propsItem }; + render(); + expect(screen.getByText(INPUT_ERRORS.wildcardPathWarning(0))).not.toBeNull(); + }); + it('should display the `AND` button', () => { const andButton = getConditionBuilderAndButton(); expect(andButton.textContent).toEqual('AND'); @@ -243,11 +349,9 @@ describe('When using the Trusted App Form', () => { describe('and when the AND button is clicked', () => { beforeEach(() => { const andButton = getConditionBuilderAndButton(); - reactTestingLibrary.act(() => { - fireEvent.click(andButton, { button: 1 }); - }); + userEvent.click(andButton, { button: 1 }); // re-render with updated `newTrustedApp` - formProps.trustedApp = formProps.onChange.mock.calls[0][0].item; + formProps.item = formProps.onChange.mock.calls[0][0].item; rerender(); }); @@ -270,127 +374,105 @@ describe('When using the Trusted App Form', () => { describe('the Policy Selection area', () => { beforeEach(() => { - const policy = generator.generatePolicyPackagePolicy(); - policy.name = 'test policy A'; - policy.id = '123'; - - formProps.policies.options = [policy]; + formProps.policies = createPolicies(); }); it('should have `global` switch on if effective scope is global and policy options hidden', () => { render(); const globalButton = renderResult.getByTestId( - `${dataTestSubjForForm}-effectedPolicies-global` + `${formPrefix}-effectedPolicies-global` ) as HTMLButtonElement; expect(globalButton.classList.contains('euiButtonGroupButton-isSelected')).toEqual(true); - expect(renderResult.queryByTestId('policy-123')).toBeNull(); + expect( + renderResult.queryByTestId(`${formPrefix}-effectedPolicies-policiesSelectable`) + ).toBeNull(); + expect(renderResult.queryByTestId('policy-id-0')).toBeNull(); }); it('should have policy options visible and specific policies checked if scope is per-policy', () => { - (formProps.trustedApp as NewTrustedApp).effectScope = { - type: 'policy', - policies: ['123'], - }; + formProps.item.tags = [formProps.policies.map((p) => `policy:${p.id}`)[0]]; render(); + const perPolicyButton = renderResult.getByTestId( - `${dataTestSubjForForm}-effectedPolicies-perPolicy` + `${formPrefix}-effectedPolicies-perPolicy` ) as HTMLButtonElement; expect(perPolicyButton.classList.contains('euiButtonGroupButton-isSelected')).toEqual(true); - expect(renderResult.getByTestId('policy-123').getAttribute('aria-disabled')).toEqual('false'); - expect(renderResult.getByTestId('policy-123').getAttribute('aria-checked')).toEqual('true'); + expect(renderResult.getByTestId('policy-id-0').getAttribute('aria-disabled')).toEqual( + 'false' + ); + expect(renderResult.getByTestId('policy-id-0-checkbox')).toBeChecked(); }); + it('should show loader when setting `policies.isLoading` to true and scope is per-policy', () => { - formProps.policies.isLoading = true; - (formProps.trustedApp as NewTrustedApp).effectScope = { - type: 'policy', - policies: ['123'], - }; + formProps.policiesIsLoading = true; + formProps.item.tags = [formProps.policies.map((p) => `policy:${p.id}`)[0]]; render(); expect(renderResult.queryByTestId('loading-spinner')).not.toBeNull(); }); }); - describe('the Policy Selection area under feature flag', () => { - it("shouldn't display the policiy selection area ", () => { - useIsExperimentalFeatureEnabledMock.mockReturnValue(false); - render(); - expect( - renderResult.queryByText('Apply trusted application globally') - ).not.toBeInTheDocument(); - }); - }); - describe('the Policy Selection area when the license downgrades to gold or below', () => { beforeEach(() => { - // select per policy for trusted app - const policy = generator.generatePolicyPackagePolicy(); - policy.name = 'test policy A'; - policy.id = '123'; - - formProps.policies.options = [policy]; - - (formProps.trustedApp as NewTrustedApp).effectScope = { - type: 'policy', - policies: ['123'], - }; - - formProps.isEditMode = true; - + const policies = createPolicies(); + formProps.policies = policies; + formProps.item.tags = [policies.map((p) => `policy:${p.id}`)[0]]; + formProps.mode = 'edit'; // downgrade license (licenseService.isPlatinumPlus as jest.Mock).mockReturnValue(false); + render(); }); it('maintains policy configuration but does not allow the user to edit add/remove individual policies in edit mode', () => { - render(); const perPolicyButton = renderResult.getByTestId( - `${dataTestSubjForForm}-effectedPolicies-perPolicy` + `${formPrefix}-effectedPolicies-perPolicy` ) as HTMLButtonElement; - expect(perPolicyButton.classList.contains('euiButtonGroupButton-isSelected')).toEqual(true); - expect(renderResult.getByTestId('policy-123').getAttribute('aria-disabled')).toEqual('true'); - expect(renderResult.getByTestId('policy-123-checkbox')).toBeChecked(); + expect(renderResult.getByTestId('policy-id-0').getAttribute('aria-disabled')).toEqual('true'); + expect(renderResult.getByTestId('policy-id-0-checkbox')).toBeChecked(); }); it("allows the user to set the trusted app entry to 'Global' in the edit option", () => { - render(); const globalButtonInput = renderResult.getByTestId('globalPolicy') as HTMLButtonElement; - - reactTestingLibrary.act(() => { + act(() => { fireEvent.click(globalButtonInput); }); - expect(formProps.onChange.mock.calls[0][0].item.effectScope.type).toBe('global'); + const policyItem = formProps.onChange.mock.calls[0][0].item.tags + ? formProps.onChange.mock.calls[0][0].item.tags[0] + : ''; + expect(policyItem).toBe('policy:all'); }); + it('hides the policy assignment section if the TA is set to global', () => { - (formProps.trustedApp as NewTrustedApp).effectScope = { - type: 'global', - }; - expect(renderResult.queryByTestId(`${dataTestSubjForForm}-effectedPolicies`)).toBeNull(); + formProps.item.tags = ['policy:all']; + rerender(); + expect(renderResult.queryByTestId(`${formPrefix}-effectedPolicies`)).toBeNull(); }); it('hides the policy assignment section if the user is adding a new TA', () => { - formProps.isEditMode = false; - expect(renderResult.queryByTestId(`${dataTestSubjForForm}-effectedPolicies`)).toBeNull(); + formProps.mode = 'create'; + rerender(); + expect(renderResult.queryByTestId(`${formPrefix}-effectedPolicies`)).toBeNull(); }); }); describe('and the user visits required fields but does not fill them out', () => { beforeEach(() => { render(); - reactTestingLibrary.act(() => { + act(() => { fireEvent.blur(getNameField()); }); - reactTestingLibrary.act(() => { + act(() => { fireEvent.blur(getConditionValue(getCondition())); }); }); it('should show Name validation error', () => { - expect(renderResult.getByText('Name is required')); + expect(renderResult.getByText(INPUT_ERRORS.name)).not.toBeNull(); }); it('should show Condition validation error', () => { - expect(renderResult.getByText('[1] Field entry must have a value')); + expect(renderResult.getByText(INPUT_ERRORS.mustHaveValue(0))); }); it('should NOT display any other errors', () => { @@ -403,129 +485,107 @@ describe('When using the Trusted App Form', () => { it('should validate that Name has a non empty space value', () => { setTextFieldValue(getNameField(), ' '); - expect(renderResult.getByText('Name is required')); + expect(renderResult.getByText(INPUT_ERRORS.name)); }); it('should validate invalid Hash value', () => { setTextFieldValue(getConditionValue(getCondition()), 'someHASH'); - expect(renderResult.getByText('[1] Invalid hash value')); + expect(renderResult.getByText(INPUT_ERRORS.invalidHash(0))); }); it('should validate that a condition value has a non empty space value', () => { setTextFieldValue(getConditionValue(getCondition()), ' '); - expect(renderResult.getByText('[1] Field entry must have a value')); + expect(renderResult.getByText(INPUT_ERRORS.mustHaveValue(0))); }); it('should validate all condition values (when multiples exist) have non empty space value', () => { const andButton = getConditionBuilderAndButton(); - reactTestingLibrary.act(() => { - fireEvent.click(andButton, { button: 1 }); - }); - rerenderWithLatestTrustedApp(); + userEvent.click(andButton, { button: 1 }); + rerenderWithLatestProps(); setTextFieldValue(getConditionValue(getCondition()), 'someHASH'); - rerenderWithLatestTrustedApp(); + rerenderWithLatestProps(); - expect(renderResult.getByText('[2] Field entry must have a value')); + expect(renderResult.getByText(INPUT_ERRORS.mustHaveValue(1))); }); it('should validate duplicated conditions', () => { const andButton = getConditionBuilderAndButton(); - reactTestingLibrary.act(() => { - fireEvent.click(andButton, { button: 1 }); - }); + userEvent.click(andButton, { button: 1 }); setTextFieldValue(getConditionValue(getCondition()), ''); - rerenderWithLatestTrustedApp(); + rerenderWithLatestProps(); - expect(renderResult.getByText('Hash cannot be added more than once')); + expect(renderResult.getByText(INPUT_ERRORS.noDuplicateField(ConditionEntryField.HASH))); }); it('should validate multiple errors in form', () => { const andButton = getConditionBuilderAndButton(); - reactTestingLibrary.act(() => { - fireEvent.click(andButton, { button: 1 }); - }); - rerenderWithLatestTrustedApp(); + userEvent.click(andButton, { button: 1 }); + rerenderWithLatestProps(); setTextFieldValue(getConditionValue(getCondition()), 'someHASH'); - rerenderWithLatestTrustedApp(); - expect(renderResult.getByText('[1] Invalid hash value')); - expect(renderResult.getByText('[2] Field entry must have a value')); + rerenderWithLatestProps(); + expect(renderResult.getByText(INPUT_ERRORS.invalidHash(0))); + expect(renderResult.getByText(INPUT_ERRORS.mustHaveValue(1))); }); }); describe('and all required data passes validation', () => { it('should call change callback with isValid set to true and contain the new item', () => { + const propsItem: Partial = { + os_types: [OperatingSystem.LINUX], + name: 'Some process', + description: 'some description', + entries: [ + createEntry(ConditionEntryField.HASH, 'match', 'e50fb1a0e5fff590ece385082edc6c41'), + ], + }; + formProps.item = { ...formProps.item, ...propsItem }; render(); - - setTextFieldValue(getNameField(), 'Some Process'); - rerenderWithLatestTrustedApp(); - - setTextFieldValue(getConditionValue(getCondition()), 'e50fb1a0e5fff590ece385082edc6c41'); - rerenderWithLatestTrustedApp(); - - setTextFieldValue(getDescriptionField(), 'some description'); - rerenderWithLatestTrustedApp(); + act(() => { + fireEvent.blur(getNameField()); + }); expect(getAllValidationErrors()).toHaveLength(0); - expect(formProps.onChange).toHaveBeenLastCalledWith({ + const expected = createOnChangeArgs({ isValid: true, - item: { - name: 'Some Process', - description: 'some description', - os: OperatingSystem.WINDOWS, - effectScope: { type: 'global' }, - entries: [ - { - field: ConditionEntryField.HASH, - operator: 'included', - type: 'match', - value: 'e50fb1a0e5fff590ece385082edc6c41', - }, - ], - }, + item: createItem(propsItem), }); + expect(formProps.onChange).toHaveBeenCalledWith(expected); }); it('should not validate form to true if name input is empty', () => { - const props = { + const propsItem: Partial = { name: 'some name', - description: '', - effectScope: { - type: 'global', - }, - os: OperatingSystem.WINDOWS, - entries: [ - { field: ConditionEntryField.PATH, operator: 'included', type: 'wildcard', value: 'x' }, - ], - } as NewTrustedApp; - - formProps.trustedApp = props; + os_types: [OperatingSystem.LINUX], + entries: [createEntry(ConditionEntryField.PATH, 'wildcard', '/sys/usr*/doc.app')], + }; + formProps.item = { ...formProps.item, ...propsItem }; render(); + expect(getAllValidationErrors()).toHaveLength(0); + expect(getAllValidationWarnings()).toHaveLength(0); - formProps.trustedApp = { - ...props, - name: '', - }; + formProps.item.name = ''; rerender(); - - expect(getAllValidationErrors()).toHaveLength(0); - expect(getAllValidationWarnings()).toHaveLength(1); + act(() => { + fireEvent.blur(getNameField()); + }); + expect(getAllValidationErrors()).toHaveLength(1); + expect(getAllValidationWarnings()).toHaveLength(0); expect(formProps.onChange).toHaveBeenLastCalledWith({ isValid: false, item: { + ...formProps.item, name: '', - description: '', - os: OperatingSystem.WINDOWS, - effectScope: { type: 'global' }, + os_types: [OperatingSystem.LINUX], entries: [ { field: ConditionEntryField.PATH, operator: 'included', type: 'wildcard', - value: 'x', + value: '/sys/usr*/doc.app', }, ], }, diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/form.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/form.tsx new file mode 100644 index 0000000000000..9656b5821b756 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/form.tsx @@ -0,0 +1,519 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { ChangeEventHandler, memo, useCallback, useEffect, useMemo, useState } from 'react'; +import { + EuiFieldText, + EuiForm, + EuiFormRow, + EuiHorizontalRule, + EuiSuperSelect, + EuiSuperSelectOption, + EuiTextArea, + EuiText, + EuiTitle, + EuiSpacer, +} from '@elastic/eui'; +import { + hasSimpleExecutableName, + isPathValid, + ConditionEntryField, + OperatingSystem, + AllConditionEntryFields, + EntryTypes, +} from '@kbn/securitysolution-utils'; +import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; + +import { TrustedAppConditionEntry, NewTrustedApp } from '../../../../../../common/endpoint/types'; +import { + isValidHash, + getDuplicateFields, +} from '../../../../../../common/endpoint/service/artifacts/validations'; + +import { + isArtifactGlobal, + getPolicyIdsFromArtifact, +} from '../../../../../../common/endpoint/service/artifacts'; +import { + isMacosLinuxTrustedAppCondition, + isWindowsTrustedAppCondition, +} from '../../state/type_guards'; + +import { + CONDITIONS_HEADER, + CONDITIONS_HEADER_DESCRIPTION, + DETAILS_HEADER, + DETAILS_HEADER_DESCRIPTION, + DESCRIPTION_LABEL, + INPUT_ERRORS, + NAME_LABEL, + POLICY_SELECT_DESCRIPTION, + SELECT_OS_LABEL, +} from '../translations'; +import { OS_TITLES } from '../../../../common/translations'; +import { LogicalConditionBuilder, LogicalConditionBuilderProps } from './logical_condition'; +import { useTestIdGenerator } from '../../../../components/hooks/use_test_id_generator'; +import { useLicense } from '../../../../../common/hooks/use_license'; +import { + EffectedPolicySelect, + EffectedPolicySelection, +} from '../../../../components/effected_policy_select'; +import { + GLOBAL_ARTIFACT_TAG, + BY_POLICY_ARTIFACT_TAG_PREFIX, +} from '../../../../../../common/endpoint/service/artifacts/constants'; +import type { PolicyData } from '../../../../../../common/endpoint/types'; +import { ArtifactFormComponentProps } from '../../../../components/artifact_list_page'; +import { isGlobalPolicyEffected } from '../../../../components/effected_policy_select/utils'; + +interface FieldValidationState { + /** If this fields state is invalid. Drives display of errors on the UI */ + isInvalid: boolean; + errors: React.ReactNode[]; + warnings: React.ReactNode[]; +} +interface ValidationResult { + /** Overall indicator if form is valid */ + isValid: boolean; + + /** Individual form field validations */ + result: Partial<{ + [key in keyof NewTrustedApp]: FieldValidationState; + }>; +} + +const addResultToValidation = ( + validation: ValidationResult, + field: keyof NewTrustedApp, + type: 'warnings' | 'errors', + resultValue: React.ReactNode +) => { + if (!validation.result[field]) { + validation.result[field] = { + isInvalid: false, + errors: [], + warnings: [], + }; + } + const errorMarkup: React.ReactNode = type === 'warnings' ?
{resultValue}
: resultValue; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + validation.result[field]![type].push(errorMarkup); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + validation.result[field]!.isInvalid = true; +}; + +const validateValues = (values: ArtifactFormComponentProps['item']): ValidationResult => { + let isValid: ValidationResult['isValid'] = true; + const validation: ValidationResult = { + isValid, + result: {}, + }; + + // Name field + if (!values.name.trim()) { + isValid = false; + addResultToValidation(validation, 'name', 'errors', INPUT_ERRORS.name); + } + + if (!values.os_types) { + isValid = false; + addResultToValidation(validation, 'os', 'errors', INPUT_ERRORS.os); + } + + const os = ((values.os_types ?? [])[0] as OperatingSystem) ?? OperatingSystem.WINDOWS; + if (!values.entries.length) { + isValid = false; + addResultToValidation(validation, 'entries', 'errors', INPUT_ERRORS.field); + } else { + const duplicated = getDuplicateFields(values.entries as TrustedAppConditionEntry[]); + if (duplicated.length) { + isValid = false; + duplicated.forEach((field: ConditionEntryField) => { + addResultToValidation( + validation, + 'entries', + 'errors', + INPUT_ERRORS.noDuplicateField(field) + ); + }); + } + values.entries.forEach((entry, index) => { + const isValidPathEntry = isPathValid({ + os, + field: entry.field as AllConditionEntryFields, + type: entry.type as EntryTypes, + value: (entry as TrustedAppConditionEntry).value, + }); + + if (!entry.field || !(entry as TrustedAppConditionEntry).value.trim()) { + isValid = false; + addResultToValidation(validation, 'entries', 'errors', INPUT_ERRORS.mustHaveValue(index)); + } else if ( + entry.field === ConditionEntryField.HASH && + !isValidHash((entry as TrustedAppConditionEntry).value) + ) { + isValid = false; + addResultToValidation(validation, 'entries', 'errors', INPUT_ERRORS.invalidHash(index)); + } else if (!isValidPathEntry) { + addResultToValidation(validation, 'entries', 'warnings', INPUT_ERRORS.pathWarning(index)); + } else if ( + isValidPathEntry && + !hasSimpleExecutableName({ + os, + value: (entry as TrustedAppConditionEntry).value, + type: entry.type as EntryTypes, + }) + ) { + addResultToValidation( + validation, + 'entries', + 'warnings', + INPUT_ERRORS.wildcardPathWarning(index) + ); + } + }); + } + + validation.isValid = isValid; + return validation; +}; + +const defaultConditionEntry = (): TrustedAppConditionEntry => ({ + field: ConditionEntryField.HASH, + operator: 'included', + type: 'match', + value: '', +}); + +export const TrustedAppsForm = memo( + ({ item, policies, policiesIsLoading, onChange, mode }) => { + const getTestId = useTestIdGenerator('trustedApps-form'); + const [visited, setVisited] = useState< + Partial<{ + [key in keyof NewTrustedApp]: boolean; + }> + >({}); + + const [selectedPolicies, setSelectedPolicies] = useState([]); + const isPlatinumPlus = useLicense().isPlatinumPlus(); + const isGlobal = useMemo(() => isArtifactGlobal(item as ExceptionListItemSchema), [item]); + const [wasByPolicy, setWasByPolicy] = useState(!isGlobalPolicyEffected(item.tags)); + const [hasFormChanged, setHasFormChanged] = useState(false); + + useEffect(() => { + if (!hasFormChanged && item.tags) { + setWasByPolicy(!isGlobalPolicyEffected(item.tags)); + } + }, [item.tags, hasFormChanged]); + + // select policies if editing + useEffect(() => { + if (hasFormChanged) { + return; + } + const policyIds = item.tags ? getPolicyIdsFromArtifact({ tags: item.tags }) : []; + if (!policyIds.length) { + return; + } + const policiesData = policies.filter((policy) => policyIds.includes(policy.id)); + + setSelectedPolicies(policiesData); + }, [hasFormChanged, item, policies]); + + const showAssignmentSection = useMemo(() => { + return ( + isPlatinumPlus || + (mode === 'edit' && (!isGlobal || (wasByPolicy && isGlobal && hasFormChanged))) + ); + }, [mode, isGlobal, hasFormChanged, isPlatinumPlus, wasByPolicy]); + + const [validationResult, setValidationResult] = useState(() => + validateValues(item) + ); + + const processChanged = useCallback( + (updatedFormValues: ArtifactFormComponentProps['item']) => { + const updatedValidationResult = validateValues(updatedFormValues); + setValidationResult(updatedValidationResult); + onChange({ + item: updatedFormValues, + isValid: updatedValidationResult.isValid, + }); + }, + [onChange] + ); + + const handleOnPolicyChange = useCallback( + (change: EffectedPolicySelection) => { + const tags = change.isGlobal + ? [GLOBAL_ARTIFACT_TAG] + : change.selected.map((policy) => `${BY_POLICY_ARTIFACT_TAG_PREFIX}${policy.id}`); + + const nextItem = { ...item, tags }; + // Preserve old selected policies when switching to global + if (!change.isGlobal) { + setSelectedPolicies(change.selected); + } + processChanged(nextItem); + setHasFormChanged(true); + }, + [item, processChanged] + ); + + const handleOnNameOrDescriptionChange = useCallback< + ChangeEventHandler + >( + (event: React.ChangeEvent) => { + const nextItem = { + ...item, + [event.target.name]: event.target.value, + }; + + processChanged(nextItem); + setHasFormChanged(true); + }, + [item, processChanged] + ); + + const handleOnNameBlur = useCallback( + ({ target: { name } }) => { + processChanged(item); + setVisited((prevVisited) => ({ ...prevVisited, [name]: true })); + }, + [item, processChanged] + ); + + const osOptions: Array> = useMemo( + () => + [OperatingSystem.LINUX, OperatingSystem.MAC, OperatingSystem.WINDOWS].map((os) => ({ + value: os, + inputDisplay: OS_TITLES[os], + })), + [] + ); + + const handleOnOsChange = useCallback( + (os: OperatingSystem) => { + setVisited((prevVisited) => { + return { + ...prevVisited, + os: true, + }; + }); + + const nextItem: ArtifactFormComponentProps['item'] = { + ...item, + os_types: [os], + entries: [] as ArtifactFormComponentProps['item']['entries'], + }; + + if (os !== OperatingSystem.WINDOWS) { + const macOsLinuxConditionEntry = item.entries.filter((entry) => + isMacosLinuxTrustedAppCondition(entry as TrustedAppConditionEntry) + ); + nextItem.entries.push(...macOsLinuxConditionEntry); + if (item.entries.length === 0) { + nextItem.entries.push(defaultConditionEntry()); + } + } else { + nextItem.entries.push(...item.entries); + } + + processChanged(nextItem); + setHasFormChanged(true); + }, + [item, processChanged] + ); + + const handleConditionBuilderOnVisited: LogicalConditionBuilderProps['onVisited'] = + useCallback(() => { + setVisited((prevState) => { + return { + ...prevState, + entries: true, + }; + }); + }, []); + + const handleEntryChange = useCallback( + (newEntry, oldEntry) => { + const nextItem: ArtifactFormComponentProps['item'] = { + ...item, + entries: item.entries.map((e) => { + if (e === oldEntry) { + return newEntry; + } + return e; + }), + }; + + processChanged(nextItem); + setHasFormChanged(true); + }, + [item, processChanged] + ); + + const handleEntryRemove = useCallback( + (entry: NewTrustedApp['entries'][0]) => { + const nextItem: ArtifactFormComponentProps['item'] = { + ...item, + entries: item.entries.filter((e) => e !== entry), + }; + + processChanged(nextItem); + setHasFormChanged(true); + }, + [item, processChanged] + ); + + const handleAndClick = useCallback(() => { + const nextItem: ArtifactFormComponentProps['item'] = { + ...item, + entries: [], + }; + const os = ((item.os_types ?? [])[0] as OperatingSystem) ?? OperatingSystem.WINDOWS; + if (os === OperatingSystem.WINDOWS) { + nextItem.entries = [...item.entries, defaultConditionEntry()].filter((entry) => + isWindowsTrustedAppCondition(entry as TrustedAppConditionEntry) + ); + } else { + nextItem.entries = [ + ...item.entries.filter((entry) => + isMacosLinuxTrustedAppCondition(entry as TrustedAppConditionEntry) + ), + defaultConditionEntry(), + ]; + } + processChanged(nextItem); + setHasFormChanged(true); + }, [item, processChanged]); + + const selectedOs = useMemo((): OperatingSystem => { + if (!item?.os_types?.length) { + return OperatingSystem.WINDOWS; + } + return item.os_types[0] as OperatingSystem; + }, [item?.os_types]); + + const trustedApp = useMemo(() => { + const ta = item; + + ta.entries = item.entries.length + ? (item.entries as TrustedAppConditionEntry[]) + : [defaultConditionEntry()]; + + return ta; + }, [item]); + + return ( + + +

{DETAILS_HEADER}

+
+ + {mode === 'create' && ( + +

{DETAILS_HEADER_DESCRIPTION}

+
+ )} + + + + + + + + + +

{CONDITIONS_HEADER}

+
+ + {CONDITIONS_HEADER_DESCRIPTION} + + + + + + + + {showAssignmentSection ? ( + <> + + + + + + ) : null} +
+ ); + } +); + +TrustedAppsForm.displayName = 'TrustedAppsForm'; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap deleted file mode 100644 index 8b496dcb519b9..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,12717 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`TrustedAppsGrid renders correctly initially 1`] = ` -.c1 { - position: relative; - padding-top: 4px; -} - -.c1 .body { - min-height: 40px; -} - -.c1 .body-content { - position: relative; -} - -.c0 .trusted-app + .trusted-app { - margin-top: 24px; -} - -
-
-
-
-
-
-
-
-
- - No items found - -
-
-
-
-
-
-
-
-
-`; - -exports[`TrustedAppsGrid renders correctly when failed loading data for the first time 1`] = ` -.c1 { - position: relative; - padding-top: 4px; -} - -.c1 .body { - min-height: 40px; -} - -.c1 .body-content { - position: relative; -} - -.c0 .trusted-app + .trusted-app { - margin-top: 24px; -} - -
-
-
-
-
-
-
-
- - - Intenal Server Error -
-
-
-
-
-
-
-
-`; - -exports[`TrustedAppsGrid renders correctly when failed loading data for the second time 1`] = ` -.c1 { - position: relative; - padding-top: 4px; -} - -.c1 .body { - min-height: 40px; -} - -.c1 .body-content { - position: relative; -} - -.c0 .trusted-app + .trusted-app { - margin-top: 24px; -} - -
-
-
-
-
-
-
-
- - - Intenal Server Error -
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
- -
-
-
-
-
-
-`; - -exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` -.c1 { - position: relative; - padding-top: 4px; -} - -.c1 .body { - min-height: 40px; -} - -.c1 .body-content { - position: relative; -} - -.c6 { - padding-top: 2px; -} - -.c5 { - margin-bottom: 4px !important; -} - -.c7 { - margin: 6px; -} - -.c3.artifactEntryCard + .c2.artifactEntryCard { - margin-top: 24px; -} - -.c4 { - padding: 32px; -} - -.c4.top-section { - padding-bottom: 24px; -} - -.c4.bottom-section { - padding-top: 24px; -} - -.c4.artifact-entry-collapsible-card { - padding: 24px !important; -} - -.c0 .trusted-app + .trusted-app { - margin-top: 24px; -} - -
-
-
-
-
-
-
-
-
-
-
-

- trusted app 0 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 0 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Windows - - - -
-
-
-
-
-
-
-
-
-
-

- trusted app 1 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 1 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Mac - - - -
-
-
-
-
-
-
-
-
-
-

- trusted app 2 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 2 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Linux - - - -
-
-
-
-
-
-
-
-
-
-

- trusted app 3 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 3 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Windows - - - -
-
-
-
-
-
-
-
-
-
-

- trusted app 4 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 4 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Mac - - - -
-
-
-
-
-
-
-
-
-
-

- trusted app 5 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 5 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Linux - - - -
-
-
-
-
-
-
-
-
-
-

- trusted app 6 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 6 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Windows - - - -
-
-
-
-
-
-
-
-
-
-

- trusted app 7 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 7 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Mac - - - -
-
-
-
-
-
-
-
-
-
-

- trusted app 8 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 8 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Linux - - - -
-
-
-
-
-
-
-
-
-
-

- trusted app 9 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 9 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Windows - - - -
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
- -
-
-
-
-
-
-`; - -exports[`TrustedAppsGrid renders correctly when loading data for the first time 1`] = ` -.c1 { - position: relative; - padding-top: 4px; -} - -.c1 .body { - min-height: 40px; -} - -.c1 .body-content { - position: relative; -} - -.c0 .trusted-app + .trusted-app { - margin-top: 24px; -} - -
-
-
-
-
-
-
-
-
-
-`; - -exports[`TrustedAppsGrid renders correctly when loading data for the second time 1`] = ` -.c1 { - position: relative; - padding-top: 4px; -} - -.c1 .body { - min-height: 40px; -} - -.c1 .body-content { - position: relative; -} - -.c6 { - padding-top: 2px; -} - -.c5 { - margin-bottom: 4px !important; -} - -.c7 { - margin: 6px; -} - -.c3.artifactEntryCard + .c2.artifactEntryCard { - margin-top: 24px; -} - -.c4 { - padding: 32px; -} - -.c4.top-section { - padding-bottom: 24px; -} - -.c4.bottom-section { - padding-top: 24px; -} - -.c4.artifact-entry-collapsible-card { - padding: 24px !important; -} - -.c0 .trusted-app + .trusted-app { - margin-top: 24px; -} - -
-
-
-
-
-
-
-
-
-
-
-
-

- trusted app 0 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 0 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Windows - - - -
-
-
-
-
-
-
-
-
-
-

- trusted app 1 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 1 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Mac - - - -
-
-
-
-
-
-
-
-
-
-

- trusted app 2 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 2 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Linux - - - -
-
-
-
-
-
-
-
-
-
-

- trusted app 3 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 3 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Windows - - - -
-
-
-
-
-
-
-
-
-
-

- trusted app 4 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 4 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Mac - - - -
-
-
-
-
-
-
-
-
-
-

- trusted app 5 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 5 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Linux - - - -
-
-
-
-
-
-
-
-
-
-

- trusted app 6 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 6 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Windows - - - -
-
-
-
-
-
-
-
-
-
-

- trusted app 7 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 7 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Mac - - - -
-
-
-
-
-
-
-
-
-
-

- trusted app 8 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 8 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Linux - - - -
-
-
-
-
-
-
-
-
-
-

- trusted app 9 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 9 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Windows - - - -
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
- -
-
-
-
-
-
-`; - -exports[`TrustedAppsGrid renders correctly when new page and page size set (not loading yet) 1`] = ` -.c1 { - position: relative; - padding-top: 4px; -} - -.c1 .body { - min-height: 40px; -} - -.c1 .body-content { - position: relative; -} - -.c6 { - padding-top: 2px; -} - -.c5 { - margin-bottom: 4px !important; -} - -.c7 { - margin: 6px; -} - -.c3.artifactEntryCard + .c2.artifactEntryCard { - margin-top: 24px; -} - -.c4 { - padding: 32px; -} - -.c4.top-section { - padding-bottom: 24px; -} - -.c4.bottom-section { - padding-top: 24px; -} - -.c4.artifact-entry-collapsible-card { - padding: 24px !important; -} - -.c0 .trusted-app + .trusted-app { - margin-top: 24px; -} - -
-
-
-
-
-
-
-
-
-
-
-

- trusted app 0 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 0 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Windows - - - -
-
-
-
-
-
-
-
-
-
-

- trusted app 1 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 1 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Mac - - - -
-
-
-
-
-
-
-
-
-
-

- trusted app 2 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 2 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Linux - - - -
-
-
-
-
-
-
-
-
-
-

- trusted app 3 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 3 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Windows - - - -
-
-
-
-
-
-
-
-
-
-

- trusted app 4 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 4 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Mac - - - -
-
-
-
-
-
-
-
-
-
-

- trusted app 5 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 5 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Linux - - - -
-
-
-
-
-
-
-
-
-
-

- trusted app 6 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 6 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Windows - - - -
-
-
-
-
-
-
-
-
-
-

- trusted app 7 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 7 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Mac - - - -
-
-
-
-
-
-
-
-
-
-

- trusted app 8 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 8 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Linux - - - -
-
-
-
-
-
-
-
-
-
-

- trusted app 9 -

-
-
-
-
-
-
- -
-
-
-
-
- Last updated -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Created -
-
-
-
- - 1 minute ago - -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - Created by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
- - - - Updated by - - - -
-
-
-
- -
-
-
- someone -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Applied globally -
-
-
-
-
-
-
- Trusted App 9 -
-
-
-
-
-
- - - - - - OS - - - - - IS - - - - Windows - - - -
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
- -
-
-
-
-
-
-`; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.stories.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.stories.tsx deleted file mode 100644 index 682d689c7e82c..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.stories.tsx +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { Provider } from 'react-redux'; -import { ThemeProvider } from 'styled-components'; -import { storiesOf } from '@storybook/react'; -import { euiLightVars } from '@kbn/ui-theme'; -import { EuiHorizontalRule } from '@elastic/eui'; - -import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; - -import { - createGlobalNoMiddlewareStore, - createListFailedResourceState, - createListLoadedResourceState, - createListLoadingResourceState, - createTrustedAppsListResourceStateChangedAction, -} from '../../../test_utils'; - -import { TrustedAppsGrid } from '.'; - -const now = 111111; - -const renderGrid = (store: ReturnType) => ( - - 'MMM D, YYYY @ HH:mm:ss.SSS' } }}> - ({ eui: euiLightVars, darkMode: false })}> - - - - - - - - -); - -storiesOf('TrustedApps/TrustedAppsGrid', module) - .add('default', () => { - return renderGrid(createGlobalNoMiddlewareStore()); - }) - .add('loading', () => { - const store = createGlobalNoMiddlewareStore(); - - store.dispatch( - createTrustedAppsListResourceStateChangedAction(createListLoadingResourceState()) - ); - - return renderGrid(store); - }) - .add('error', () => { - const store = createGlobalNoMiddlewareStore(); - - store.dispatch( - createTrustedAppsListResourceStateChangedAction( - createListFailedResourceState('Intenal Server Error') - ) - ); - - return renderGrid(store); - }) - .add('loaded', () => { - const store = createGlobalNoMiddlewareStore(); - - store.dispatch( - createTrustedAppsListResourceStateChangedAction( - createListLoadedResourceState({ pageSize: 10 }, now) - ) - ); - - return renderGrid(store); - }) - .add('loading second time', () => { - const store = createGlobalNoMiddlewareStore(); - - store.dispatch( - createTrustedAppsListResourceStateChangedAction( - createListLoadingResourceState(createListLoadedResourceState({ pageSize: 10 }, now)) - ) - ); - - return renderGrid(store); - }) - .add('long texts', () => { - const store = createGlobalNoMiddlewareStore(); - - store.dispatch( - createTrustedAppsListResourceStateChangedAction( - createListLoadedResourceState({ pageSize: 10 }, now, true) - ) - ); - - return renderGrid(store); - }); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.test.tsx deleted file mode 100644 index 6cc292b5a6d3e..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.test.tsx +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { render, act } from '@testing-library/react'; -import React from 'react'; -import { Provider } from 'react-redux'; - -import { - createSampleTrustedApp, - createListFailedResourceState, - createListLoadedResourceState, - createListLoadingResourceState, - createTrustedAppsListResourceStateChangedAction, - createUserChangedUrlAction, - createGlobalNoMiddlewareStore, -} from '../../../test_utils'; -import { TrustedAppsGrid } from '.'; -import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; - -jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ - htmlIdGenerator: () => () => 'mockId', -})); - -jest.mock('../../../../../../common/lib/kibana'); - -const now = 111111; - -const renderList = (store: ReturnType) => { - const Wrapper: React.FC = ({ children }) => ( - - {children} - - ); - - return render(, { wrapper: Wrapper }); -}; - -describe('TrustedAppsGrid', () => { - it('renders correctly initially', () => { - expect(renderList(createGlobalNoMiddlewareStore()).container).toMatchSnapshot(); - }); - - it('renders correctly when loading data for the first time', () => { - const store = createGlobalNoMiddlewareStore(); - - store.dispatch( - createTrustedAppsListResourceStateChangedAction(createListLoadingResourceState()) - ); - - expect(renderList(store).container).toMatchSnapshot(); - }); - - it('renders correctly when failed loading data for the first time', () => { - const store = createGlobalNoMiddlewareStore(); - - store.dispatch( - createTrustedAppsListResourceStateChangedAction( - createListFailedResourceState('Intenal Server Error') - ) - ); - - expect(renderList(store).container).toMatchSnapshot(); - }); - - it('renders correctly when loaded data', () => { - const store = createGlobalNoMiddlewareStore(); - - store.dispatch( - createTrustedAppsListResourceStateChangedAction( - createListLoadedResourceState({ pageSize: 10 }, now) - ) - ); - - expect(renderList(store).container).toMatchSnapshot(); - }); - - it('renders correctly when new page and page size set (not loading yet)', () => { - const store = createGlobalNoMiddlewareStore(); - - store.dispatch( - createTrustedAppsListResourceStateChangedAction( - createListLoadedResourceState({ pageSize: 10 }, now) - ) - ); - store.dispatch( - createUserChangedUrlAction('/administration/trusted_apps', '?page_index=2&page_size=50') - ); - - expect(renderList(store).container).toMatchSnapshot(); - }); - - it('renders correctly when loading data for the second time', () => { - const store = createGlobalNoMiddlewareStore(); - - store.dispatch( - createTrustedAppsListResourceStateChangedAction( - createListLoadingResourceState(createListLoadedResourceState({ pageSize: 10 }, now)) - ) - ); - - expect(renderList(store).container).toMatchSnapshot(); - }); - - it('renders correctly when failed loading data for the second time', () => { - const store = createGlobalNoMiddlewareStore(); - - store.dispatch( - createTrustedAppsListResourceStateChangedAction( - createListFailedResourceState( - 'Intenal Server Error', - createListLoadedResourceState({ pageSize: 10 }, now) - ) - ) - ); - - expect(renderList(store).container).toMatchSnapshot(); - }); - - it('triggers deletion dialog when delete action clicked', async () => { - const store = createGlobalNoMiddlewareStore(); - - store.dispatch( - createTrustedAppsListResourceStateChangedAction( - createListLoadedResourceState({ pageSize: 10 }, now) - ) - ); - store.dispatch = jest.fn(); - - const renderResult = renderList(store); - - await act(async () => { - (await renderResult.findAllByTestId('trustedAppCard-header-actions-button'))[0].click(); - }); - - await act(async () => { - (await renderResult.findByTestId('deleteTrustedAppAction')).click(); - }); - - expect(store.dispatch).toBeCalledWith({ - type: 'trustedAppDeletionDialogStarted', - payload: { - entry: createSampleTrustedApp(0), - }, - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.tsx deleted file mode 100644 index a8e2187083523..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.tsx +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { memo, useCallback, useMemo } from 'react'; - -import { useHistory } from 'react-router-dom'; -import styled from 'styled-components'; -import { i18n } from '@kbn/i18n'; -import { useDispatch } from 'react-redux'; -import { Dispatch } from 'redux'; -import { Pagination } from '../../../state'; - -import { - getCurrentLocation, - getListErrorMessage, - getListItems, - getListPagination, - isListLoading, - getMapOfPoliciesById, - isLoadingListOfPolicies, -} from '../../../store/selectors'; - -import { useTrustedAppsNavigateCallback, useTrustedAppsSelector } from '../../hooks'; - -import { getPolicyDetailPath, getTrustedAppsListPath } from '../../../../../common/routing'; -import { - PaginatedContent, - PaginatedContentProps, -} from '../../../../../components/paginated_content'; -import { PolicyDetailsRouteState, TrustedApp } from '../../../../../../../common/endpoint/types'; -import { - ArtifactEntryCard, - ArtifactEntryCardProps, -} from '../../../../../components/artifact_entry_card'; -import { AppAction } from '../../../../../../common/store/actions'; -import { APP_UI_ID } from '../../../../../../../common/constants'; -import { useAppUrl } from '../../../../../../common/lib/kibana'; - -export interface PaginationBarProps { - pagination: Pagination; - onChange: (pagination: { size: number; index: number }) => void; -} - -type ArtifactEntryCardType = typeof ArtifactEntryCard; - -const RootWrapper = styled.div` - .trusted-app + .trusted-app { - margin-top: ${({ theme }) => theme.eui.spacerSizes.l}; - } -`; - -const BACK_TO_TRUSTED_APPS_LABEL = i18n.translate( - 'xpack.securitySolution.trustedapps.grid.policyDetailsLinkBackLabel', - { defaultMessage: 'Back to trusted applications' } -); - -const EDIT_TRUSTED_APP_ACTION_LABEL = i18n.translate( - 'xpack.securitySolution.trustedapps.grid.cardAction.edit', - { - defaultMessage: 'Edit trusted application', - } -); - -const DELETE_TRUSTED_APP_ACTION_LABEL = i18n.translate( - 'xpack.securitySolution.trustedapps.grid.cardAction.delete', - { - defaultMessage: 'Delete trusted application', - } -); - -export const TrustedAppsGrid = memo(() => { - const history = useHistory(); - const dispatch = useDispatch>(); - const { getAppUrl } = useAppUrl(); - - const pagination = useTrustedAppsSelector(getListPagination); - const listItems = useTrustedAppsSelector(getListItems); - const isLoading = useTrustedAppsSelector(isListLoading); - const error = useTrustedAppsSelector(getListErrorMessage); - const location = useTrustedAppsSelector(getCurrentLocation); - const policyListById = useTrustedAppsSelector(getMapOfPoliciesById); - const loadingPoliciesList = useTrustedAppsSelector(isLoadingListOfPolicies); - - const handlePaginationChange: PaginatedContentProps< - TrustedApp, - ArtifactEntryCardType - >['onChange'] = useTrustedAppsNavigateCallback(({ pageIndex, pageSize }) => ({ - page_index: pageIndex, - page_size: pageSize, - })); - - const artifactCardPropsPerItem = useMemo(() => { - const cachedCardProps: Record = {}; - - // Casting `listItems` below to remove the `Immutable<>` from it in order to prevent errors - // with common component's props - for (const trustedApp of listItems as TrustedApp[]) { - let policies: ArtifactEntryCardProps['policies']; - - if (trustedApp.effectScope.type === 'policy' && trustedApp.effectScope.policies.length) { - policies = trustedApp.effectScope.policies.reduce< - Required['policies'] - >((policyToNavOptionsMap, policyId) => { - const currentPagePath = getTrustedAppsListPath({ - ...location, - }); - - const policyDetailsPath = getPolicyDetailPath(policyId); - - const routeState: PolicyDetailsRouteState = { - backLink: { - label: BACK_TO_TRUSTED_APPS_LABEL, - navigateTo: [ - APP_UI_ID, - { - path: currentPagePath, - }, - ], - href: getAppUrl({ path: currentPagePath }), - }, - onCancelNavigateTo: [ - APP_UI_ID, - { - path: currentPagePath, - }, - ], - }; - - policyToNavOptionsMap[policyId] = { - navigateAppId: APP_UI_ID, - navigateOptions: { - path: policyDetailsPath, - state: routeState, - }, - href: getAppUrl({ path: policyDetailsPath }), - children: policyListById[policyId]?.name ?? policyId, - target: '_blank', - }; - return policyToNavOptionsMap; - }, {}); - } - - cachedCardProps[trustedApp.id] = { - item: trustedApp, - policies, - loadingPoliciesList, - hideComments: true, - 'data-test-subj': 'trustedAppCard', - actions: [ - { - icon: 'controlsHorizontal', - onClick: () => { - history.push( - getTrustedAppsListPath({ - ...location, - show: 'edit', - id: trustedApp.id, - }) - ); - }, - 'data-test-subj': 'editTrustedAppAction', - children: EDIT_TRUSTED_APP_ACTION_LABEL, - }, - { - icon: 'trash', - onClick: () => { - dispatch({ - type: 'trustedAppDeletionDialogStarted', - payload: { entry: trustedApp }, - }); - }, - 'data-test-subj': 'deleteTrustedAppAction', - children: DELETE_TRUSTED_APP_ACTION_LABEL, - }, - ], - hideDescription: !trustedApp.description, - }; - } - - return cachedCardProps; - }, [dispatch, getAppUrl, history, listItems, location, policyListById, loadingPoliciesList]); - - const handleArtifactCardProps = useCallback( - (trustedApp: TrustedApp) => { - return artifactCardPropsPerItem[trustedApp.id]; - }, - [artifactCardPropsPerItem] - ); - - return ( - - - items={listItems as TrustedApp[]} - onChange={handlePaginationChange} - ItemComponent={ArtifactEntryCard} - itemComponentProps={handleArtifactCardProps} - loading={isLoading} - itemId="id" - error={error} - pagination={pagination} - /> - - ); -}); - -TrustedAppsGrid.displayName = 'TrustedAppsGrid'; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/view_type_toggle/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/view_type_toggle/__snapshots__/index.test.tsx.snap deleted file mode 100644 index 09a13f11d2adb..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/view_type_toggle/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,53 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`view_type_toggle ViewTypeToggle should render grid selection correctly 1`] = ` - -`; - -exports[`view_type_toggle ViewTypeToggle should render list selection correctly 1`] = ` - -`; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/view_type_toggle/index.stories.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/view_type_toggle/index.stories.tsx deleted file mode 100644 index 8ba70769838a3..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/view_type_toggle/index.stories.tsx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useState } from 'react'; -import { ThemeProvider } from 'styled-components'; -import { storiesOf, addDecorator } from '@storybook/react'; -import { euiLightVars } from '@kbn/ui-theme'; - -import { ViewType } from '../../../state'; -import { ViewTypeToggle } from '.'; - -addDecorator((storyFn) => ( - ({ eui: euiLightVars, darkMode: false })}>{storyFn()} -)); - -const useRenderStory = (viewType: ViewType) => { - const [selectedOption, setSelectedOption] = useState(viewType); - - return ; -}; - -storiesOf('TrustedApps/ViewTypeToggle', module) - .add('grid selected', () => useRenderStory('grid')) - .add('list selected', () => useRenderStory('list')); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/view_type_toggle/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/view_type_toggle/index.test.tsx deleted file mode 100644 index d6b2bb5a2e7ef..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/view_type_toggle/index.test.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { render } from '@testing-library/react'; -import { shallow } from 'enzyme'; -import React from 'react'; - -import { ViewTypeToggle } from '.'; - -describe('view_type_toggle', () => { - describe('ViewTypeToggle', () => { - it('should render grid selection correctly', () => { - const element = shallow( {}} />); - - expect(element).toMatchSnapshot(); - }); - - it('should render list selection correctly', () => { - const element = shallow( {}} />); - - expect(element).toMatchSnapshot(); - }); - - it('should trigger onToggle', async () => { - const onToggle = jest.fn(); - const element = render(); - - (await element.findAllByTestId('viewTypeToggleButton'))[0].click(); - - expect(onToggle).toBeCalledWith('grid'); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/view_type_toggle/index.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/view_type_toggle/index.tsx deleted file mode 100644 index 700358d6beef6..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/view_type_toggle/index.tsx +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { memo, useCallback } from 'react'; -import { EuiButtonGroup } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; -import { ViewType } from '../../../state'; -import { GRID_VIEW_TOGGLE_LABEL, LIST_VIEW_TOGGLE_LABEL } from '../../translations'; - -export interface ViewTypeToggleProps { - selectedOption: ViewType; - onToggle: (type: ViewType) => void; -} - -export const ViewTypeToggle = memo(({ selectedOption, onToggle }: ViewTypeToggleProps) => { - const handleChange = useCallback( - (id) => { - if (id === 'list' || id === 'grid') { - onToggle(id); - } - }, - [onToggle] - ); - - return ( - - ); -}); - -ViewTypeToggle.displayName = 'ViewTypeToggle'; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/hooks.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/hooks.ts deleted file mode 100644 index 4c2747d74d31e..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/hooks.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useDispatch, useSelector } from 'react-redux'; -import { useHistory } from 'react-router-dom'; -import { useCallback } from 'react'; - -import { State } from '../../../../common/store'; - -import { - MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE as TRUSTED_APPS_NS, - MANAGEMENT_STORE_GLOBAL_NAMESPACE as GLOBAL_NS, -} from '../../../common/constants'; - -import { AppAction } from '../../../../common/store/actions'; -import { getTrustedAppsListPath } from '../../../common/routing'; -import { TrustedAppsListPageLocation, TrustedAppsListPageState } from '../state'; -import { getCurrentLocation } from '../store/selectors'; - -export function useTrustedAppsSelector(selector: (state: TrustedAppsListPageState) => R): R { - return useSelector((state: State) => - selector(state[GLOBAL_NS][TRUSTED_APPS_NS] as TrustedAppsListPageState) - ); -} - -export type NavigationCallback = ( - ...args: Parameters[0]> -) => Partial; - -export function useTrustedAppsNavigateCallback(callback: NavigationCallback) { - const location = useTrustedAppsSelector(getCurrentLocation); - const history = useHistory(); - - return useCallback( - (...args) => history.push(getTrustedAppsListPath({ ...location, ...callback(...args) })), - // TODO: needs more investigation, but if callback is in dependencies list memoization will never happen - // eslint-disable-next-line react-hooks/exhaustive-deps - [history, location] - ); -} - -export function useTrustedAppsStoreActionCallback( - callback: (...args: Parameters[0]>) => AppAction -) { - const dispatch = useDispatch(); - - // eslint-disable-next-line react-hooks/exhaustive-deps - return useCallback((...args) => dispatch(callback(...args)), [dispatch]); -} diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/index.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/index.ts deleted file mode 100644 index 78c22fc020a45..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export * from './trusted_apps_page'; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts index 3d8a56ad74315..b5b92f3a686ea 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts @@ -13,14 +13,54 @@ import { OperatorFieldIds, } from '../../../../../common/endpoint/types'; -export { OS_TITLES } from '../../../common/translations'; - export const ABOUT_TRUSTED_APPS = i18n.translate('xpack.securitySolution.trustedapps.aboutInfo', { defaultMessage: 'Add a trusted application to improve performance or alleviate conflicts with other applications running on ' + 'your hosts.', }); +export const NAME_LABEL = i18n.translate('xpack.securitySolution.trustedApps.name.label', { + defaultMessage: 'Name', +}); + +export const DETAILS_HEADER = i18n.translate('xpack.securitySolution.trustedApps.details.header', { + defaultMessage: 'Details', +}); + +export const DETAILS_HEADER_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.trustedApps.details.header.description', + { + defaultMessage: + 'Trusted applications improve performance or alleviate conflicts with other applications running on your hosts.', + } +); + +export const DESCRIPTION_LABEL = i18n.translate( + 'xpack.securitySolution.trustedapps.create.description', + { + defaultMessage: 'Description', + } +); + +export const CONDITIONS_HEADER = i18n.translate( + 'xpack.securitySolution.trustedApps.conditions.header', + { + defaultMessage: 'Conditions', + } +); + +export const CONDITIONS_HEADER_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.trustedApps.conditions.header.description', + { + defaultMessage: + 'Select an operating system and add conditions. Availability of conditions may depend on your chosen OS.', + } +); + +export const SELECT_OS_LABEL = i18n.translate('xpack.securitySolution.trustedApps.os.label', { + defaultMessage: 'Select operating system', +}); + export const CONDITION_FIELD_TITLE: { [K in ConditionEntryField]: string } = { [ConditionEntryField.HASH]: i18n.translate( 'xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field.hash', @@ -74,23 +114,50 @@ export const ENTRY_PROPERTY_TITLES: Readonly<{ }), }; -export const GRID_VIEW_TOGGLE_LABEL = i18n.translate( - 'xpack.securitySolution.trustedapps.view.toggle.grid', +export const POLICY_SELECT_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.trustedApps.assignmentSectionDescription', { - defaultMessage: 'Grid view', + defaultMessage: + 'Assign this trusted application globally across all policies, or assign it to specific policies.', } ); -export const LIST_VIEW_TOGGLE_LABEL = i18n.translate( - 'xpack.securitySolution.trustedapps.view.toggle.list', - { - defaultMessage: 'List view', - } -); - -export const SEARCH_TRUSTED_APP_PLACEHOLDER = i18n.translate( - 'xpack.securitySolution.trustedapps.list.search.placeholder', - { - defaultMessage: 'Search on the fields below: name, description, value', - } -); +export const INPUT_ERRORS = { + name: i18n.translate('xpack.securitySolution.trustedapps.create.nameRequiredMsg', { + defaultMessage: 'Name is required', + }), + os: i18n.translate('xpack.securitySolution.trustedapps.create.osRequiredMsg', { + defaultMessage: 'Operating System is required', + }), + field: i18n.translate('xpack.securitySolution.trustedapps.create.conditionRequiredMsg', { + defaultMessage: 'At least one Field definition is required', + }), + noDuplicateField: (field: ConditionEntryField) => + i18n.translate('xpack.securitySolution.trustedapps.create.conditionFieldDuplicatedMsg', { + defaultMessage: '{field} cannot be added more than once', + values: { field: CONDITION_FIELD_TITLE[field] }, + }), + mustHaveValue: (index: number) => + i18n.translate('xpack.securitySolution.trustedapps.create.conditionFieldValueRequiredMsg', { + defaultMessage: '[{row}] Field entry must have a value', + values: { row: index + 1 }, + }), + invalidHash: (index: number) => + i18n.translate('xpack.securitySolution.trustedapps.create.conditionFieldInvalidHashMsg', { + defaultMessage: '[{row}] Invalid hash value', + values: { row: index + 1 }, + }), + pathWarning: (index: number) => + i18n.translate('xpack.securitySolution.trustedapps.create.conditionFieldInvalidPathMsg', { + defaultMessage: '[{row}] Path may be formed incorrectly; verify value', + values: { row: index + 1 }, + }), + wildcardPathWarning: (index: number) => + i18n.translate( + 'xpack.securitySolution.trustedapps.create.conditionFieldDegradedPerformanceMsg', + { + defaultMessage: `[{row}] A wildcard in the filename will affect the endpoint's performance`, + values: { row: index + 1 }, + } + ), +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.test.tsx deleted file mode 100644 index 6abce21d7ccf5..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.test.tsx +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { Provider } from 'react-redux'; -import { render } from '@testing-library/react'; -import { I18nProvider } from '@kbn/i18n-react'; - -import { - createGlobalNoMiddlewareStore, - createSampleTrustedApp, - createServerApiError, -} from '../test_utils'; - -import { - TrustedAppDeletionDialogStarted, - TrustedAppDeletionSubmissionResourceStateChanged, -} from '../store/action'; - -import { TrustedAppDeletionDialog } from './trusted_app_deletion_dialog'; - -const renderDeletionDialog = (store: ReturnType) => { - const Wrapper: React.FC = ({ children }) => ( - - {children} - - ); - - return render(, { wrapper: Wrapper }); -}; - -const createDialogStartAction = (): TrustedAppDeletionDialogStarted => ({ - type: 'trustedAppDeletionDialogStarted', - payload: { entry: createSampleTrustedApp(3) }, -}); - -const createDialogLoadingAction = (): TrustedAppDeletionSubmissionResourceStateChanged => ({ - type: 'trustedAppDeletionSubmissionResourceStateChanged', - payload: { - newState: { - type: 'LoadingResourceState', - previousState: { type: 'UninitialisedResourceState' }, - }, - }, -}); - -const createDialogFailedAction = (): TrustedAppDeletionSubmissionResourceStateChanged => ({ - type: 'trustedAppDeletionSubmissionResourceStateChanged', - payload: { - newState: { type: 'FailedResourceState', error: createServerApiError('Not Found') }, - }, -}); - -describe('TrustedAppDeletionDialog', () => { - it('renders correctly initially', () => { - expect(renderDeletionDialog(createGlobalNoMiddlewareStore()).baseElement).toMatchSnapshot(); - }); - - it('renders correctly when dialog started', () => { - const store = createGlobalNoMiddlewareStore(); - - store.dispatch(createDialogStartAction()); - - expect(renderDeletionDialog(store).baseElement).toMatchSnapshot(); - }); - - it('renders correctly when deletion is in progress', () => { - const store = createGlobalNoMiddlewareStore(); - - store.dispatch(createDialogStartAction()); - store.dispatch(createDialogLoadingAction()); - - expect(renderDeletionDialog(store).baseElement).toMatchSnapshot(); - }); - - it('renders correctly when deletion failed', () => { - const store = createGlobalNoMiddlewareStore(); - - store.dispatch(createDialogStartAction()); - store.dispatch(createDialogFailedAction()); - - expect(renderDeletionDialog(store).baseElement).toMatchSnapshot(); - }); - - it('triggers confirmation action when confirm button clicked', async () => { - const store = createGlobalNoMiddlewareStore(); - - store.dispatch(createDialogStartAction()); - store.dispatch = jest.fn(); - - (await renderDeletionDialog(store).findByTestId('trustedAppDeletionConfirm')).click(); - - expect(store.dispatch).toBeCalledWith({ - type: 'trustedAppDeletionDialogConfirmed', - }); - }); - - it('triggers closing action when cancel button clicked', async () => { - const store = createGlobalNoMiddlewareStore(); - - store.dispatch(createDialogStartAction()); - store.dispatch = jest.fn(); - - (await renderDeletionDialog(store).findByTestId('trustedAppDeletionCancel')).click(); - - expect(store.dispatch).toBeCalledWith({ - type: 'trustedAppDeletionDialogClosed', - }); - }); - - it('does not trigger closing action when deletion in progress and cancel button clicked', async () => { - const store = createGlobalNoMiddlewareStore(); - - store.dispatch(createDialogStartAction()); - store.dispatch(createDialogLoadingAction()); - - store.dispatch = jest.fn(); - - (await renderDeletionDialog(store).findByTestId('trustedAppDeletionCancel')).click(); - - expect(store.dispatch).not.toBeCalled(); - }); -}); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.tsx deleted file mode 100644 index 458a83d37322e..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.tsx +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - EuiButtonEmpty, - EuiCallOut, - EuiModal, - EuiModalBody, - EuiModalFooter, - EuiModalHeader, - EuiModalHeaderTitle, - EuiSpacer, - EuiText, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import React, { memo, useCallback, useMemo } from 'react'; -import { useDispatch } from 'react-redux'; -import { Dispatch } from 'redux'; -import { AutoFocusButton } from '../../../../common/components/autofocus_button/autofocus_button'; -import { Immutable, TrustedApp } from '../../../../../common/endpoint/types'; -import { AppAction } from '../../../../common/store/actions'; -import { isPolicyEffectScope } from '../state/type_guards'; -import { - getDeletionDialogEntry, - isDeletionDialogOpen, - isDeletionInProgress, -} from '../store/selectors'; -import { useTrustedAppsSelector } from './hooks'; - -const CANCEL_SUBJ = 'trustedAppDeletionCancel'; -const CONFIRM_SUBJ = 'trustedAppDeletionConfirm'; - -const getTranslations = (entry: Immutable | undefined) => ({ - title: ( - {entry?.name} }} - /> - ), - calloutTitle: ( - - ), - calloutMessage: ( - - ), - subMessage: ( - - ), - cancelButton: ( - - ), - confirmButton: ( - - ), -}); - -export const TrustedAppDeletionDialog = memo(() => { - const dispatch = useDispatch>(); - const isBusy = useTrustedAppsSelector(isDeletionInProgress); - const entry = useTrustedAppsSelector(getDeletionDialogEntry); - const translations = useMemo(() => getTranslations(entry), [entry]); - const onConfirm = useCallback(() => { - dispatch({ type: 'trustedAppDeletionDialogConfirmed' }); - }, [dispatch]); - const onCancel = useCallback(() => { - if (!isBusy) { - dispatch({ type: 'trustedAppDeletionDialogClosed' }); - } - }, [dispatch, isBusy]); - - if (useTrustedAppsSelector(isDeletionDialogOpen)) { - return ( - - - {translations.title} - - - - -

{translations.calloutMessage}

-
- - -

{translations.subMessage}

-
-
- - - - {translations.cancelButton} - - - - {translations.confirmButton} - - -
- ); - } else { - return <>; - } -}); - -TrustedAppDeletionDialog.displayName = 'TrustedAppDeletionDialog'; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.test.tsx new file mode 100644 index 0000000000000..f778e6f471e1f --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.test.tsx @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { act, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { TRUSTED_APPS_PATH } from '../../../../../common/constants'; +import { AppContextTestRender, createAppRootMockRenderer } from '../../../../common/mock/endpoint'; +import { TrustedAppsList } from './trusted_apps_list'; +import { exceptionsListAllHttpMocks } from '../../mocks/exceptions_list_http_mocks'; +import { SEARCHABLE_FIELDS } from '../constants'; +import { parseQueryFilterToKQL } from '../../../common/utils'; +import { useUserPrivileges as _useUserPrivileges } from '../../../../common/components/user_privileges'; + +jest.mock('../../../../common/components/user_privileges'); + +describe('When on the trusted applications page', () => { + let render: () => ReturnType; + let renderResult: ReturnType; + let history: AppContextTestRender['history']; + let mockedContext: AppContextTestRender; + let apiMocks: ReturnType; + + beforeEach(() => { + mockedContext = createAppRootMockRenderer(); + ({ history } = mockedContext); + render = () => (renderResult = mockedContext.render()); + + apiMocks = exceptionsListAllHttpMocks(mockedContext.coreStart.http); + + act(() => { + history.push(TRUSTED_APPS_PATH); + }); + }); + + it('should search using expected exception item fields', async () => { + const expectedFilterString = parseQueryFilterToKQL('fooFooFoo', SEARCHABLE_FIELDS); + const { findAllByTestId } = render(); + await waitFor(async () => { + await expect(findAllByTestId('trustedAppsListPage-card')).resolves.toHaveLength(10); + }); + + apiMocks.responseProvider.exceptionsFind.mockClear(); + userEvent.type(renderResult.getByTestId('searchField'), 'fooFooFoo'); + userEvent.click(renderResult.getByTestId('searchButton')); + await waitFor(() => { + expect(apiMocks.responseProvider.exceptionsFind).toHaveBeenCalled(); + }); + + expect(apiMocks.responseProvider.exceptionsFind).toHaveBeenLastCalledWith( + expect.objectContaining({ + query: expect.objectContaining({ + filter: expectedFilterString, + }), + }) + ); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.tsx new file mode 100644 index 0000000000000..359e9d1aeb99d --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.tsx @@ -0,0 +1,123 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { memo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { DocLinks } from '@kbn/doc-links'; +import { EuiLink } from '@elastic/eui'; + +import { useHttp } from '../../../../common/lib/kibana'; +import { ArtifactListPage, ArtifactListPageProps } from '../../../components/artifact_list_page'; +import { TrustedAppsApiClient } from '../service'; +import { TrustedAppsForm } from './components/form'; +import { SEARCHABLE_FIELDS } from '../constants'; + +const TRUSTED_APPS_PAGE_LABELS: ArtifactListPageProps['labels'] = { + pageTitle: i18n.translate('xpack.securitySolution.trustedApps.pageTitle', { + defaultMessage: 'Trusted applications', + }), + pageAboutInfo: i18n.translate('xpack.securitySolution.trustedApps.pageAboutInfo', { + defaultMessage: + 'Trusted applications improve performance or alleviate conflicts with other applications running on your hosts.', + }), + pageAddButtonTitle: i18n.translate('xpack.securitySolution.trustedApps.pageAddButtonTitle', { + defaultMessage: 'Add trusted application', + }), + getShowingCountLabel: (total) => + i18n.translate('xpack.securitySolution.trustedApps.showingTotal', { + defaultMessage: + 'Showing {total} {total, plural, one {trusted application} other {trusted applications}}', + values: { total }, + }), + cardActionEditLabel: i18n.translate('xpack.securitySolution.trustedApps.cardActionEditLabel', { + defaultMessage: 'Edit trusted application', + }), + cardActionDeleteLabel: i18n.translate( + 'xpack.securitySolution.trustedApps.cardActionDeleteLabel', + { + defaultMessage: 'Delete trusted application', + } + ), + flyoutCreateTitle: i18n.translate('xpack.securitySolution.trustedApps.flyoutCreateTitle', { + defaultMessage: 'Add trusted application', + }), + flyoutEditTitle: i18n.translate('xpack.securitySolution.trustedApps.flyoutEditTitle', { + defaultMessage: 'Edit trusted application', + }), + flyoutCreateSubmitButtonLabel: i18n.translate( + 'xpack.securitySolution.trustedApps.flyoutCreateSubmitButtonLabel', + { defaultMessage: 'Add trusted application' } + ), + flyoutCreateSubmitSuccess: ({ name }) => + i18n.translate('xpack.securitySolution.trustedApps.flyoutCreateSubmitSuccess', { + defaultMessage: '"{name}" has been added to your trusted applications.', + values: { name }, + }), + flyoutEditSubmitSuccess: ({ name }) => + i18n.translate('xpack.securitySolution.trustedApps.flyoutEditSubmitSuccess', { + defaultMessage: '"{name}" has been updated.', + values: { name }, + }), + flyoutDowngradedLicenseDocsInfo: ( + securitySolutionDocsLinks: DocLinks['securitySolution'] + ): React.ReactNode => { + return ( + <> + + + + + + ); + }, + deleteActionSuccess: (itemName) => + i18n.translate('xpack.securitySolution.trustedApps.deleteSuccess', { + defaultMessage: '"{itemName}" has been removed from trusted applications.', + values: { itemName }, + }), + emptyStateTitle: i18n.translate('xpack.securitySolution.trustedApps.emptyStateTitle', { + defaultMessage: 'Add your first trusted application', + }), + emptyStateInfo: i18n.translate('xpack.securitySolution.trustedApps.emptyStateInfo', { + defaultMessage: + 'Add a trusted application to improve performance or alleviate conflicts with other applications running on your hosts.', + }), + emptyStatePrimaryButtonLabel: i18n.translate( + 'xpack.securitySolution.trustedApps.emptyStatePrimaryButtonLabel', + { defaultMessage: 'Add trusted application' } + ), + searchPlaceholderInfo: i18n.translate( + 'xpack.securitySolution.trustedApps.searchPlaceholderInfo', + { + defaultMessage: 'Search on the fields below: name, description, value', + } + ), +}; + +export const TrustedAppsList = memo(() => { + const http = useHttp(); + const trustedAppsApiClient = TrustedAppsApiClient.getInstance(http); + + return ( + + ); +}); + +TrustedAppsList.displayName = 'TrustedAppsList'; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_notifications.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_notifications.test.tsx deleted file mode 100644 index ebd1e06abdc4f..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_notifications.test.tsx +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { Provider } from 'react-redux'; -import { render } from '@testing-library/react'; - -import { NotificationsStart } from '@kbn/core/public'; - -import { coreMock } from '@kbn/core/public/mocks'; -import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public/context'; - -import { - createGlobalNoMiddlewareStore, - createSampleTrustedApp, - createServerApiError, -} from '../test_utils'; - -import { TrustedAppsNotifications } from './trusted_apps_notifications'; - -const mockNotifications = () => coreMock.createStart({ basePath: '/mock' }).notifications; - -const renderNotifications = ( - store: ReturnType, - notifications: NotificationsStart -) => { - const Wrapper: React.FC = ({ children }) => ( - - {children} - - ); - - return render(, { wrapper: Wrapper }); -}; - -describe('TrustedAppsNotifications', () => { - it('renders correctly initially', () => { - const notifications = mockNotifications(); - - renderNotifications(createGlobalNoMiddlewareStore(), notifications); - - expect(notifications.toasts.addSuccess).not.toBeCalled(); - expect(notifications.toasts.addDanger).not.toBeCalled(); - }); - - it('shows success notification when deletion successful', () => { - const store = createGlobalNoMiddlewareStore(); - const notifications = mockNotifications(); - - renderNotifications(store, notifications); - - store.dispatch({ - type: 'trustedAppDeletionDialogStarted', - payload: { entry: createSampleTrustedApp(3) }, - }); - store.dispatch({ - type: 'trustedAppDeletionSubmissionResourceStateChanged', - payload: { newState: { type: 'LoadedResourceState', data: null } }, - }); - store.dispatch({ - type: 'trustedAppDeletionDialogClosed', - }); - - expect(notifications.toasts.addSuccess).toBeCalledWith({ - text: '"trusted app 3" has been removed from the trusted applications list.', - title: 'Successfully removed', - }); - expect(notifications.toasts.addDanger).not.toBeCalled(); - }); - - it('shows error notification when deletion fails', () => { - const store = createGlobalNoMiddlewareStore(); - const notifications = mockNotifications(); - - renderNotifications(store, notifications); - - store.dispatch({ - type: 'trustedAppDeletionDialogStarted', - payload: { entry: createSampleTrustedApp(3) }, - }); - store.dispatch({ - type: 'trustedAppDeletionSubmissionResourceStateChanged', - payload: { - newState: { type: 'FailedResourceState', error: createServerApiError('Not Found') }, - }, - }); - - expect(notifications.toasts.addSuccess).not.toBeCalled(); - expect(notifications.toasts.addDanger).toBeCalledWith({ - text: 'Unable to remove "trusted app 3" from the trusted applications list. Reason: Not Found', - title: 'Removal failure', - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_notifications.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_notifications.tsx deleted file mode 100644 index dd03636d7cc66..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_notifications.tsx +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { memo, useState } from 'react'; -import { i18n } from '@kbn/i18n'; - -import { ServerApiError } from '../../../../common/types'; -import { Immutable, NewTrustedApp, TrustedApp } from '../../../../../common/endpoint/types'; -import { - getCreationDialogFormEntry, - getDeletionDialogEntry, - getDeletionError, - isCreationSuccessful, - isDeletionSuccessful, - isEdit, -} from '../store/selectors'; - -import { useToasts } from '../../../../common/lib/kibana'; -import { useTrustedAppsSelector } from './hooks'; - -const getDeletionErrorMessage = (error: ServerApiError, entry: Immutable) => { - return { - title: i18n.translate('xpack.securitySolution.trustedapps.deletionError.title', { - defaultMessage: 'Removal failure', - }), - text: i18n.translate('xpack.securitySolution.trustedapps.deletionError.text', { - defaultMessage: - 'Unable to remove "{name}" from the trusted applications list. Reason: {message}', - values: { name: entry.name, message: error.message }, - }), - }; -}; - -const getDeletionSuccessMessage = (entry: Immutable) => { - return { - title: i18n.translate('xpack.securitySolution.trustedapps.deletionSuccess.title', { - defaultMessage: 'Successfully removed', - }), - text: i18n.translate('xpack.securitySolution.trustedapps.deletionSuccess.text', { - defaultMessage: '"{name}" has been removed from the trusted applications list.', - values: { name: entry?.name }, - }), - }; -}; - -const getCreationSuccessMessage = (entry: Immutable) => { - return { - title: i18n.translate('xpack.securitySolution.trustedapps.creationSuccess.title', { - defaultMessage: 'Success!', - }), - text: i18n.translate( - 'xpack.securitySolution.trustedapps.createTrustedAppFlyout.successToastTitle', - { - defaultMessage: '"{name}" has been added to the trusted applications list.', - values: { name: entry.name }, - } - ), - }; -}; - -const getUpdateSuccessMessage = (entry: Immutable) => { - return { - title: i18n.translate('xpack.securitySolution.trustedapps.updateSuccess.title', { - defaultMessage: 'Success!', - }), - text: i18n.translate( - 'xpack.securitySolution.trustedapps.createTrustedAppFlyout.updateSuccessToastTitle', - { - defaultMessage: '"{name}" has been updated.', - values: { name: entry.name }, - } - ), - }; -}; - -export const TrustedAppsNotifications = memo(() => { - const deletionError = useTrustedAppsSelector(getDeletionError); - const deletionDialogEntry = useTrustedAppsSelector(getDeletionDialogEntry); - const deletionSuccessful = useTrustedAppsSelector(isDeletionSuccessful); - const creationDialogNewEntry = useTrustedAppsSelector(getCreationDialogFormEntry); - const creationSuccessful = useTrustedAppsSelector(isCreationSuccessful); - const editMode = useTrustedAppsSelector(isEdit); - const toasts = useToasts(); - - const [wasAlreadyHandled] = useState(new WeakSet()); - - if (deletionError && deletionDialogEntry) { - toasts.addDanger(getDeletionErrorMessage(deletionError, deletionDialogEntry)); - } - - if (deletionSuccessful && deletionDialogEntry) { - toasts.addSuccess(getDeletionSuccessMessage(deletionDialogEntry)); - } - - if ( - creationSuccessful && - creationDialogNewEntry && - !wasAlreadyHandled.has(creationDialogNewEntry) - ) { - wasAlreadyHandled.add(creationDialogNewEntry); - - toasts.addSuccess( - (editMode && getUpdateSuccessMessage(creationDialogNewEntry)) || - getCreationSuccessMessage(creationDialogNewEntry) - ); - } - - return <>; -}); - -TrustedAppsNotifications.displayName = 'TrustedAppsNotifications'; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx deleted file mode 100644 index d3bf8fe6ed46a..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx +++ /dev/null @@ -1,885 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import * as reactTestingLibrary from '@testing-library/react'; -import { TrustedAppsPage } from './trusted_apps_page'; -import { AppContextTestRender, createAppRootMockRenderer } from '../../../../common/mock/endpoint'; -import { fireEvent } from '@testing-library/dom'; -import { MiddlewareActionSpyHelper } from '../../../../common/store/test_utils'; -import { ConditionEntryField, OperatingSystem } from '@kbn/securitysolution-utils'; -import { TrustedApp } from '../../../../../common/endpoint/types'; -import { HttpFetchOptions, HttpFetchOptionsWithPath } from '@kbn/core/public'; -import { isFailedResourceState, isLoadedResourceState } from '../state'; -import { forceHTMLElementOffsetWidth } from '../../../components/effected_policy_select/test_utils'; -import { toUpdateTrustedApp } from '../../../../../common/endpoint/service/trusted_apps/to_update_trusted_app'; -import { licenseService } from '../../../../common/hooks/use_license'; -import { FoundExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; -import { EXCEPTION_LIST_ITEM_URL } from '@kbn/securitysolution-list-constants'; -import { trustedAppsAllHttpMocks } from '../../mocks'; -import { waitFor } from '@testing-library/react'; - -jest.mock('../../../../common/hooks/use_license', () => { - const licenseServiceInstance = { - isPlatinumPlus: jest.fn(), - }; - return { - licenseService: licenseServiceInstance, - useLicense: () => { - return licenseServiceInstance; - }, - }; -}); - -jest.mock('../../../../common/components/user_privileges/endpoint/use_endpoint_privileges'); - -describe('When on the Trusted Apps Page', () => { - const expectedAboutInfo = - 'Add a trusted application to improve performance or alleviate conflicts with other ' + - 'applications running on your hosts.'; - - let mockedContext: AppContextTestRender; - let history: AppContextTestRender['history']; - let coreStart: AppContextTestRender['coreStart']; - let waitForAction: MiddlewareActionSpyHelper['waitForAction']; - let render: () => ReturnType; - let renderResult: ReturnType; - let mockedApis: ReturnType; - let getFakeTrustedApp = jest.fn(); - - const originalScrollTo = window.scrollTo; - const act = reactTestingLibrary.act; - const waitForListUI = async (): Promise => { - await waitFor(() => { - expect(renderResult.getByTestId('trustedAppsListPageContent')).toBeTruthy(); - }); - }; - - beforeAll(() => { - window.scrollTo = () => {}; - }); - - afterAll(() => { - window.scrollTo = originalScrollTo; - }); - - beforeEach(() => { - mockedContext = createAppRootMockRenderer(); - getFakeTrustedApp = jest.fn( - (): TrustedApp => ({ - id: '2d95bec3-b48f-4db7-9622-a2b061cc031d', - version: 'abc123', - name: 'Generated Exception (3xnng)', - os: OperatingSystem.WINDOWS, - created_at: '2021-01-04T13:55:00.561Z', - created_by: 'me', - updated_at: '2021-01-04T13:55:00.561Z', - updated_by: 'me', - description: 'created by ExceptionListItemGenerator', - effectScope: { type: 'global' }, - entries: [ - { - field: ConditionEntryField.PATH, - value: 'one/two', - operator: 'included', - type: 'match', - }, - ], - }) - ); - - history = mockedContext.history; - coreStart = mockedContext.coreStart; - (licenseService.isPlatinumPlus as jest.Mock).mockReturnValue(true); - waitForAction = mockedContext.middlewareSpy.waitForAction; - mockedApis = trustedAppsAllHttpMocks(coreStart.http); - render = () => (renderResult = mockedContext.render()); - reactTestingLibrary.act(() => { - history.push('/administration/trusted_apps'); - }); - window.scrollTo = jest.fn(); - }); - - afterEach(() => reactTestingLibrary.cleanup()); - - describe('and there are trusted app entries', () => { - const renderWithListData = async () => { - render(); - await act(async () => { - await waitForListUI(); - }); - - return renderResult; - }; - - it('should display subtitle info about trusted apps', async () => { - const { getByTestId } = await renderWithListData(); - expect(getByTestId('header-panel-subtitle').textContent).toEqual(expectedAboutInfo); - }); - - it('should display a Add Trusted App button', async () => { - const { getByTestId } = await renderWithListData(); - const addButton = getByTestId('trustedAppsListAddButton'); - expect(addButton.textContent).toBe('Add trusted application'); - }); - - it('should display the searchExceptions', async () => { - await renderWithListData(); - expect(await renderResult.findByTestId('searchExceptions')).not.toBeNull(); - }); - - describe('and the Grid view is being displayed', () => { - const renderWithListDataAndClickOnEditCard = async () => { - await renderWithListData(); - - await act(async () => { - // The 3rd Trusted app to be rendered will be a policy specific one - (await renderResult.findAllByTestId('trustedAppCard-header-actions-button'))[2].click(); - }); - - act(() => { - fireEvent.click(renderResult.getByTestId('editTrustedAppAction')); - }); - }; - - const renderWithListDataAndClickAddButton = async (): Promise< - ReturnType - > => { - await renderWithListData(); - - act(() => { - const addButton = renderResult.getByTestId('trustedAppsListAddButton'); - fireEvent.click(addButton, { button: 1 }); - }); - - // Wait for the policies to be loaded - await act(async () => { - await waitForAction('trustedAppsPoliciesStateChanged', { - validate: (action) => { - return isLoadedResourceState(action.payload); - }, - }); - }); - - return renderResult; - }; - - describe('the license is downgraded to gold or below and the user is editing a per policy TA', () => { - beforeEach(async () => { - (licenseService.isPlatinumPlus as jest.Mock).mockReturnValue(false); - - const originalFakeTrustedAppProvider = getFakeTrustedApp.getMockImplementation(); - getFakeTrustedApp.mockImplementation(() => { - return { - ...originalFakeTrustedAppProvider!(), - effectScope: { - type: 'policy', - policies: ['abc123'], - }, - }; - }); - await renderWithListDataAndClickOnEditCard(); - }); - - it('shows a message at the top of the flyout to inform the user their license is expired', () => { - expect( - renderResult.queryByTestId('addTrustedAppFlyout-expired-license-callout') - ).toBeTruthy(); - }); - }); - - describe('the license is downgraded to gold or below and the user is adding a new TA', () => { - beforeEach(async () => { - (licenseService.isPlatinumPlus as jest.Mock).mockReturnValue(false); - - const originalFakeTrustedAppProvider = getFakeTrustedApp.getMockImplementation(); - getFakeTrustedApp.mockImplementation(() => { - return { - ...originalFakeTrustedAppProvider!(), - effectScope: { - type: 'policy', - policies: ['abc123'], - }, - }; - }); - await renderWithListDataAndClickAddButton(); - }); - it('does not show the expired license message at the top of the flyout', async () => { - expect( - renderResult.queryByTestId('addTrustedAppFlyout-expired-license-callout') - ).toBeNull(); - }); - }); - - describe('and the edit trusted app button is clicked', () => { - beforeEach(async () => { - await renderWithListDataAndClickOnEditCard(); - }); - - it('should persist edit params to url', () => { - expect(history.location.search).toEqual( - '?show=edit&id=bec3b48f-ddb7-4622-a2b0-61cc031d17eb' - ); - }); - - it('should display the Edit flyout', () => { - expect(renderResult.getByTestId('addTrustedAppFlyout')); - }); - - it('should NOT display the about info for trusted apps', () => { - expect(renderResult.queryByTestId('addTrustedAppFlyout-about')).toBeNull(); - }); - - it('should show correct flyout title', () => { - expect(renderResult.getByTestId('addTrustedAppFlyout-headerTitle').textContent).toBe( - 'Edit trusted application' - ); - }); - - it('should display the expected text for the Save button', () => { - expect(renderResult.getByTestId('addTrustedAppFlyout-createButton').textContent).toEqual( - 'Save' - ); - }); - - it('should display trusted app data for edit', async () => { - const formNameInput = renderResult.getByTestId( - 'addTrustedAppFlyout-createForm-nameTextField' - ) as HTMLInputElement; - const formDescriptionInput = renderResult.getByTestId( - 'addTrustedAppFlyout-createForm-descriptionField' - ) as HTMLTextAreaElement; - - expect(formNameInput.value).toEqual('Generated Exception (nng74)'); - expect(formDescriptionInput.value).toEqual('created by ExceptionListItemGenerator'); - }); - - describe('and when Save is clicked', () => { - it('should call the correct api (PUT)', async () => { - await act(async () => { - fireEvent.click(renderResult.getByTestId('addTrustedAppFlyout-createButton')); - await waitForAction('trustedAppCreationSubmissionResourceStateChanged', { - validate({ payload }) { - return isLoadedResourceState(payload.newState); - }, - }); - }); - - expect(coreStart.http.put).toHaveBeenCalledTimes(1); - - const lastCallToPut = coreStart.http.put.mock.calls[0] as unknown as [ - string, - HttpFetchOptions - ]; - - expect(lastCallToPut[0]).toEqual('/api/exception_lists/items'); - - expect(JSON.parse(lastCallToPut[1].body as string)).toEqual({ - _version: '9zawi', - name: 'Generated Exception (nng74)', - description: 'created by ExceptionListItemGenerator', - entries: [ - { - field: 'process.hash.md5', - operator: 'included', - type: 'match', - value: '741462ab431a22233c787baab9b653c7', - }, - { - field: 'process.executable.caseless', - operator: 'included', - type: 'match', - value: 'c:\\fol\\bin.exe', - }, - ], - os_types: ['windows'], - tags: [ - 'policy:ddf6570b-9175-4a6d-b288-61a09771c647', - 'policy:b8e616ae-44fc-4be7-846c-ce8fa5c082dd', - ], - id: '05b5e350-0cad-4dc3-a61d-6e6796b0af39', - comments: [], - item_id: 'bec3b48f-ddb7-4622-a2b0-61cc031d17eb', - namespace_type: 'agnostic', - type: 'simple', - }); - }); - }); - }); - - describe('and attempting to show Edit panel based on URL params', () => { - const renderAndWaitForGetApi = async () => { - // the store action watcher is setup prior to render because `renderWithListData()` - // also awaits API calls and this action could be missed. - const apiResponseForEditTrustedApp = waitForAction( - 'trustedAppCreationEditItemStateChanged', - { - validate({ payload }) { - return isLoadedResourceState(payload) || isFailedResourceState(payload); - }, - } - ); - - await renderWithListData(); - - await reactTestingLibrary.act(async () => { - await apiResponseForEditTrustedApp; - }); - - return renderResult; - }; - - beforeEach(() => { - reactTestingLibrary.act(() => { - history.push('/administration/trusted_apps?show=edit&id=9999-edit-8888'); - }); - }); - - it('should retrieve trusted app via API using url `id`', async () => { - await renderAndWaitForGetApi(); - - expect(coreStart.http.get.mock.calls).toContainEqual([ - EXCEPTION_LIST_ITEM_URL, - { - query: { - item_id: '9999-edit-8888', - namespace_type: 'agnostic', - }, - }, - ]); - - expect( - ( - renderResult.getByTestId( - 'addTrustedAppFlyout-createForm-nameTextField' - ) as HTMLInputElement - ).value - ).toEqual('Generated Exception (u6kh2)'); - }); - - it('should redirect to list and show toast message if `id` is missing from URL', async () => { - reactTestingLibrary.act(() => { - history.push('/administration/trusted_apps?show=edit&id='); - }); - - await renderAndWaitForGetApi(); - - expect(history.location.search).toEqual(''); - expect(coreStart.notifications.toasts.addWarning.mock.calls[0][0]).toEqual( - 'Unable to edit trusted application (No id provided)' - ); - }); - - it('should redirect to list and show toast message on API error for GET of `id`', async () => { - // Mock the API GET for the trusted application - mockedApis.responseProvider.trustedApp.mockImplementation(() => { - throw new Error('test: api error response'); - }); - - await renderAndWaitForGetApi(); - - expect(history.location.search).toEqual(''); - expect(coreStart.notifications.toasts.addWarning.mock.calls[0][0]).toEqual( - 'Unable to edit trusted application (test: api error response)' - ); - }); - }); - }); - }); - - describe('and the Add Trusted App button is clicked', () => { - const renderAndClickAddButton = async (): Promise< - ReturnType - > => { - render(); - await act(async () => { - await Promise.all([ - waitForAction('trustedAppsListResourceStateChanged'), - waitForAction('trustedAppsExistStateChanged', { - validate({ payload }) { - return isLoadedResourceState(payload); - }, - }), - ]); - }); - - act(() => { - const addButton = renderResult.getByTestId('trustedAppsListAddButton'); - fireEvent.click(addButton, { button: 1 }); - }); - - // Wait for the policies to be loaded - await act(async () => { - await waitForAction('trustedAppsPoliciesStateChanged', { - validate: (action) => { - return isLoadedResourceState(action.payload); - }, - }); - }); - - return renderResult; - }; - - it('should display the create flyout', async () => { - const { getByTestId } = await renderAndClickAddButton(); - const flyout = getByTestId('addTrustedAppFlyout'); - expect(flyout).not.toBeNull(); - - const flyoutTitle = getByTestId('addTrustedAppFlyout-headerTitle'); - expect(flyoutTitle.textContent).toBe('Add trusted application'); - - expect(getByTestId('addTrustedAppFlyout-about')); - }); - - it('should update the URL to indicate the flyout is opened', async () => { - await renderAndClickAddButton(); - expect(/show\=create/.test(history.location.search)).toBe(true); - }); - - it('should preserve other URL search params', async () => { - const createListResponse = - mockedApis.responseProvider.trustedAppsList.getMockImplementation()!; - mockedApis.responseProvider.trustedAppsList.mockImplementation((...args) => { - const response = createListResponse(...args); - response.total = 100; // Trigger the UI to show pagination - return response; - }); - - reactTestingLibrary.act(() => { - history.push('/administration/trusted_apps?page_index=2&page_size=20'); - }); - await renderAndClickAddButton(); - expect(history.location.search).toBe('?page_index=2&page_size=20&show=create'); - }); - - it('should display create form', async () => { - const { queryByTestId } = await renderAndClickAddButton(); - expect(queryByTestId('addTrustedAppFlyout-createForm')).not.toBeNull(); - }); - - it('should have list of policies populated', async () => { - const resetEnv = forceHTMLElementOffsetWidth(); - await renderAndClickAddButton(); - act(() => { - fireEvent.click(renderResult.getByTestId('perPolicy')); - }); - expect(renderResult.getByTestId('policy-ddf6570b-9175-4a6d-b288-61a09771c647')); - resetEnv(); - }); - - it('should initially have the flyout Add button disabled', async () => { - const { getByTestId } = await renderAndClickAddButton(); - expect((getByTestId('addTrustedAppFlyout-createButton') as HTMLButtonElement).disabled).toBe( - true - ); - }); - - it('should close flyout if cancel button is clicked', async () => { - const { getByTestId, queryByTestId } = await renderAndClickAddButton(); - const cancelButton = getByTestId('addTrustedAppFlyout-cancelButton'); - await reactTestingLibrary.act(async () => { - fireEvent.click(cancelButton, { button: 1 }); - await waitForAction('trustedAppCreationDialogClosed'); - }); - expect(history.location.search).toBe(''); - expect(queryByTestId('addTrustedAppFlyout')).toBeNull(); - }); - - it('should close flyout if flyout close button is clicked', async () => { - const { getByTestId, queryByTestId } = await renderAndClickAddButton(); - const flyoutCloseButton = getByTestId('euiFlyoutCloseButton'); - await reactTestingLibrary.act(async () => { - fireEvent.click(flyoutCloseButton, { button: 1 }); - await waitForAction('trustedAppCreationDialogClosed'); - }); - expect(queryByTestId('addTrustedAppFlyout')).toBeNull(); - expect(history.location.search).toBe(''); - }); - - describe('and when the form data is valid', () => { - const fillInCreateForm = async () => { - mockedContext.store.dispatch({ - type: 'trustedAppCreationDialogFormStateUpdated', - payload: { - isValid: true, - entry: toUpdateTrustedApp(getFakeTrustedApp()), - }, - }); - }; - - it('should enable the Flyout Add button', async () => { - await renderAndClickAddButton(); - await fillInCreateForm(); - - const flyoutAddButton = renderResult.getByTestId( - 'addTrustedAppFlyout-createButton' - ) as HTMLButtonElement; - - expect(flyoutAddButton.disabled).toBe(false); - }); - - describe('and the Flyout Add button is clicked', () => { - let releasePostCreateApi: () => void; - - beforeEach(async () => { - // Add a delay to the create api response provider and expose a function that allows - // us to release it at the right time. - mockedApis.responseProvider.trustedAppCreate.mockDelay.mockReturnValue( - new Promise((resolve) => { - releasePostCreateApi = resolve as typeof releasePostCreateApi; - }) - ); - - await renderAndClickAddButton(); - await fillInCreateForm(); - - const userClickedSaveActionWatcher = waitForAction('trustedAppCreationDialogConfirmed'); - reactTestingLibrary.act(() => { - fireEvent.click(renderResult.getByTestId('addTrustedAppFlyout-createButton'), { - button: 1, - }); - }); - - await reactTestingLibrary.act(async () => { - await userClickedSaveActionWatcher; - }); - }); - - afterEach(() => releasePostCreateApi()); - - it('should display info about Trusted Apps', async () => { - expect(renderResult.getByTestId('addTrustedAppFlyout-about').textContent).toEqual( - expectedAboutInfo - ); - }); - - it('should disable the Cancel button', async () => { - expect( - (renderResult.getByTestId('addTrustedAppFlyout-cancelButton') as HTMLButtonElement) - .disabled - ).toBe(true); - releasePostCreateApi(); - }); - - it('should hide the dialog close button', async () => { - expect(renderResult.queryByTestId('euiFlyoutCloseButton')).toBeNull(); - }); - - it('should disable the flyout Add button and set it to loading', async () => { - const saveButton = renderResult.getByTestId( - 'addTrustedAppFlyout-createButton' - ) as HTMLButtonElement; - expect(saveButton.disabled).toBe(true); - expect(saveButton.querySelector('.euiLoadingSpinner')).not.toBeNull(); - }); - - describe('and if create was successful', () => { - beforeEach(async () => { - await reactTestingLibrary.act(async () => { - const serverResponseAction = waitForAction( - 'trustedAppCreationSubmissionResourceStateChanged' - ); - - coreStart.http.get.mockClear(); - releasePostCreateApi(); - await serverResponseAction; - }); - }); - - it('should close the flyout', () => { - expect(renderResult.queryByTestId('addTrustedAppFlyout')).toBeNull(); - }); - - it('should show success toast notification', () => { - expect(coreStart.notifications.toasts.addSuccess.mock.calls[0][0]).toEqual({ - text: '"Generated Exception (3xnng)" has been added to the trusted applications list.', - title: 'Success!', - }); - }); - - it('should trigger the List to reload', () => { - const isCalled = coreStart.http.get.mock.calls.some( - (call) => call[0].toString() === `${EXCEPTION_LIST_ITEM_URL}/_find` - ); - expect(isCalled).toEqual(true); - }); - }); - - describe('and if create failed', () => { - const ServerErrorResponseBodyMock = class extends Error { - public readonly body: { message: string }; - constructor(message = 'Test - Bad Call') { - super(message); - this.body = { - message, - }; - } - }; - beforeEach(async () => { - const failedCreateApiResponse = new ServerErrorResponseBodyMock(); - - mockedApis.responseProvider.trustedAppCreate.mockImplementation(() => { - throw failedCreateApiResponse; - }); - - await reactTestingLibrary.act(async () => { - const serverResponseAction = waitForAction( - 'trustedAppCreationSubmissionResourceStateChanged', - { - validate({ payload }) { - return isFailedResourceState(payload.newState); - }, - } - ); - - releasePostCreateApi(); - await serverResponseAction; - }); - }); - - it('should continue to show the flyout', () => { - expect(renderResult.getByTestId('addTrustedAppFlyout')).not.toBeNull(); - }); - - it('should enable the Cancel Button', () => { - expect( - (renderResult.getByTestId('addTrustedAppFlyout-cancelButton') as HTMLButtonElement) - .disabled - ).toBe(false); - }); - - it('should show the dialog close button', () => { - expect(renderResult.getByTestId('euiFlyoutCloseButton')).not.toBeNull(); - }); - - it('should enable the flyout Add button and remove loading indicating', () => { - expect( - (renderResult.getByTestId('addTrustedAppFlyout-createButton') as HTMLButtonElement) - .disabled - ).toBe(false); - }); - - it('should show API errors in the form', () => { - expect(renderResult.container.querySelector('.euiForm__errors')).not.toBeNull(); - }); - }); - }); - }); - - describe('and when the form data is not valid', () => { - it('should not enable the Flyout Add button with an invalid hash', async () => { - await renderAndClickAddButton(); - const { getByTestId } = renderResult; - - reactTestingLibrary.act(() => { - fireEvent.change(getByTestId('addTrustedAppFlyout-createForm-nameTextField'), { - target: { value: 'trusted app A' }, - }); - - fireEvent.change( - getByTestId('addTrustedAppFlyout-createForm-conditionsBuilder-group1-entry0-value'), - { target: { value: 'invalid hash' } } - ); - }); - - const flyoutAddButton = getByTestId( - 'addTrustedAppFlyout-createButton' - ) as HTMLButtonElement; - expect(flyoutAddButton.disabled).toBe(true); - }); - }); - }); - - describe('and there are no trusted apps', () => { - const releaseExistsResponse = jest.fn((): FoundExceptionListItemSchema => { - return { - data: [], - total: 0, - page: 1, - per_page: 1, - }; - }); - const releaseListResponse = jest.fn((): FoundExceptionListItemSchema => { - return { - data: [], - total: 0, - page: 1, - per_page: 20, - }; - }); - - beforeEach(() => { - mockedApis.responseProvider.trustedAppsList.mockImplementation(({ query }) => { - const { page, per_page: perPage } = query as { page: number; per_page: number }; - - if (page === 1 && perPage === 1) { - return releaseExistsResponse(); - } else { - return releaseListResponse(); - } - }); - }); - - afterEach(() => { - releaseExistsResponse.mockClear(); - releaseListResponse.mockClear(); - }); - - it('should show a loader until trusted apps existence can be confirmed', async () => { - render(); - expect(await renderResult.findByTestId('trustedAppsListLoader')).not.toBeNull(); - }); - - it('should show Empty Prompt if not entries exist', async () => { - render(); - await act(async () => { - await waitForAction('trustedAppsExistStateChanged'); - }); - expect(await renderResult.findByTestId('trustedAppEmptyState')).not.toBeNull(); - }); - - it('should hide empty prompt and show list after one trusted app is added', async () => { - render(); - await act(async () => { - await waitForAction('trustedAppsExistStateChanged'); - }); - expect(await renderResult.findByTestId('trustedAppEmptyState')).not.toBeNull(); - releaseListResponse.mockReturnValueOnce({ - data: [mockedApis.responseProvider.trustedApp({ query: {} } as HttpFetchOptionsWithPath)], - total: 1, - page: 1, - per_page: 20, - }); - releaseExistsResponse.mockReturnValueOnce({ - data: [mockedApis.responseProvider.trustedApp({ query: {} } as HttpFetchOptionsWithPath)], - total: 1, - page: 1, - per_page: 1, - }); - - await act(async () => { - mockedContext.store.dispatch({ - type: 'trustedAppsListDataOutdated', - }); - await waitForAction('trustedAppsListResourceStateChanged'); - }); - - expect(await renderResult.findByTestId('trustedAppsListPageContent')).not.toBeNull(); - }); - - it('should should show empty prompt once the last trusted app entry is deleted', async () => { - releaseListResponse.mockReturnValueOnce({ - data: [mockedApis.responseProvider.trustedApp({ query: {} } as HttpFetchOptionsWithPath)], - total: 1, - page: 1, - per_page: 20, - }); - releaseExistsResponse.mockReturnValueOnce({ - data: [mockedApis.responseProvider.trustedApp({ query: {} } as HttpFetchOptionsWithPath)], - total: 1, - page: 1, - per_page: 1, - }); - - render(); - - await act(async () => { - await waitForAction('trustedAppsExistStateChanged'); - }); - - expect(await renderResult.findByTestId('trustedAppsListPageContent')).not.toBeNull(); - - await act(async () => { - mockedContext.store.dispatch({ - type: 'trustedAppsListDataOutdated', - }); - await waitForAction('trustedAppsListResourceStateChanged'); - }); - - expect(await renderResult.findByTestId('trustedAppEmptyState')).not.toBeNull(); - }); - - it('should not display the searchExceptions', async () => { - render(); - await act(async () => { - await waitForAction('trustedAppsExistStateChanged'); - }); - expect(renderResult.queryByTestId('searchExceptions')).toBeNull(); - }); - }); - - describe('and the search is dispatched', () => { - beforeEach(async () => { - reactTestingLibrary.act(() => { - history.push('/administration/trusted_apps?filter=test'); - }); - render(); - await act(async () => { - await waitForListUI(); - }); - }); - - it('search bar is filled with query params', () => { - expect(renderResult.getByDisplayValue('test')).not.toBeNull(); - }); - - it('search action is dispatched', async () => { - await act(async () => { - fireEvent.click(renderResult.getByTestId('searchButton')); - expect(await waitForAction('userChangedUrl')).not.toBeNull(); - }); - }); - }); - - describe('and the back button is present', () => { - beforeEach(async () => { - render(); - await act(async () => { - await waitForListUI(); - }); - reactTestingLibrary.act(() => { - history.push('/administration/trusted_apps', { - onBackButtonNavigateTo: [{ appId: 'appId' }], - backButtonLabel: 'back to fleet', - backButtonUrl: '/fleet', - }); - }); - }); - - it('back button is present', () => { - const button = renderResult.queryByTestId('backToOrigin'); - expect(button).not.toBeNull(); - expect(button).toHaveAttribute('href', '/fleet'); - }); - - it('back button is present after push history', () => { - reactTestingLibrary.act(() => { - history.push('/administration/trusted_apps'); - }); - const button = renderResult.queryByTestId('backToOrigin'); - expect(button).not.toBeNull(); - expect(button).toHaveAttribute('href', '/fleet'); - }); - }); - - describe('and the back button is not present', () => { - beforeEach(async () => { - render(); - await act(async () => { - await waitForAction('trustedAppsListResourceStateChanged'); - }); - reactTestingLibrary.act(() => { - history.push('/administration/trusted_apps'); - }); - }); - - it('back button is not present when missing history params', () => { - const button = renderResult.queryByTestId('backToOrigin'); - expect(button).toBeNull(); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.tsx deleted file mode 100644 index 6b3f59f44ce12..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.tsx +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { memo, useMemo, useCallback } from 'react'; -import { useDispatch } from 'react-redux'; -import { Dispatch } from 'redux'; -import { useLocation } from 'react-router-dom'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { - checkingIfEntriesExist, - entriesExist, - getCurrentLocation, - getListTotalItemsCount, - listOfPolicies, - prevEntriesExist, -} from '../store/selectors'; -import { useTrustedAppsNavigateCallback, useTrustedAppsSelector } from './hooks'; -import { AdministrationListPage } from '../../../components/administration_list_page'; -import { CreateTrustedAppFlyout } from './components/create_trusted_app_flyout'; -import { TrustedAppsGrid } from './components/trusted_apps_grid'; -import { TrustedAppDeletionDialog } from './trusted_app_deletion_dialog'; -import { TrustedAppsNotifications } from './trusted_apps_notifications'; -import { AppAction } from '../../../../common/store/actions'; -import { ABOUT_TRUSTED_APPS, SEARCH_TRUSTED_APP_PLACEHOLDER } from './translations'; -import { EmptyState } from './components/empty_state'; -import { SearchExceptions } from '../../../components/search_exceptions'; -import { BackToExternalAppSecondaryButton } from '../../../components/back_to_external_app_secondary_button'; -import { BackToExternalAppButton } from '../../../components/back_to_external_app_button'; -import { ListPageRouteState } from '../../../../../common/endpoint/types'; -import { ManagementPageLoader } from '../../../components/management_page_loader'; -import { useMemoizedRouteState } from '../../../common/hooks'; - -export const TrustedAppsPage = memo(() => { - const dispatch = useDispatch>(); - const { state: routeState } = useLocation(); - const location = useTrustedAppsSelector(getCurrentLocation); - const totalItemsCount = useTrustedAppsSelector(getListTotalItemsCount); - const isCheckingIfEntriesExists = useTrustedAppsSelector(checkingIfEntriesExist); - const policyList = useTrustedAppsSelector(listOfPolicies); - const doEntriesExist = useTrustedAppsSelector(entriesExist); - const didEntriesExist = useTrustedAppsSelector(prevEntriesExist); - const navigationCallbackQuery = useTrustedAppsNavigateCallback( - (query: string, includedPolicies?: string) => ({ - filter: query, - included_policies: includedPolicies, - }) - ); - - const memoizedRouteState = useMemoizedRouteState(routeState); - - const backButtonEmptyComponent = useMemo(() => { - if (memoizedRouteState && memoizedRouteState.onBackButtonNavigateTo) { - return ; - } - }, [memoizedRouteState]); - - const backButtonHeaderComponent = useMemo(() => { - if (memoizedRouteState && memoizedRouteState.onBackButtonNavigateTo) { - return ; - } - }, [memoizedRouteState]); - - const handleAddButtonClick = useTrustedAppsNavigateCallback(() => ({ - show: 'create', - id: undefined, - })); - const handleAddFlyoutClose = useTrustedAppsNavigateCallback(() => ({ - show: undefined, - id: undefined, - })); - - const handleOnSearch = useCallback( - (query: string, includedPolicies?: string) => { - dispatch({ type: 'trustedAppForceRefresh', payload: { forceRefresh: true } }); - navigationCallbackQuery(query, includedPolicies); - }, - [dispatch, navigationCallbackQuery] - ); - - const showCreateFlyout = !!location.show; - - const canDisplayContent = useCallback( - () => doEntriesExist || (isCheckingIfEntriesExists && didEntriesExist), - [didEntriesExist, doEntriesExist, isCheckingIfEntriesExists] - ); - - const addButton = ( - - - - ); - - const content = ( - <> - - - {showCreateFlyout && ( - - )} - - {canDisplayContent() ? ( - <> - - - - - - {i18n.translate('xpack.securitySolution.trustedapps.list.totalCount', { - defaultMessage: - 'Showing {totalItemsCount, plural, one {# trusted application} other {# trusted applications}}', - values: { totalItemsCount }, - })} - - - - - - - - - - ) : ( - - )} - - ); - - return ( - - } - subtitle={ABOUT_TRUSTED_APPS} - actions={addButton} - hideHeader={!canDisplayContent()} - > - - - {isCheckingIfEntriesExists && !didEntriesExist ? ( - - ) : ( - content - )} - - ); -}); - -TrustedAppsPage.displayName = 'TrustedAppsPage'; diff --git a/x-pack/plugins/security_solution/public/management/store/middleware.ts b/x-pack/plugins/security_solution/public/management/store/middleware.ts index d011a9dcb91a7..86a5ade340058 100644 --- a/x-pack/plugins/security_solution/public/management/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/store/middleware.ts @@ -14,12 +14,10 @@ import { MANAGEMENT_STORE_ENDPOINTS_NAMESPACE, MANAGEMENT_STORE_GLOBAL_NAMESPACE, MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE, - MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE, MANAGEMENT_STORE_EVENT_FILTERS_NAMESPACE, } from '../common/constants'; import { policyDetailsMiddlewareFactory } from '../pages/policy/store/policy_details'; import { endpointMiddlewareFactory } from '../pages/endpoint_hosts/store/middleware'; -import { trustedAppsPageMiddlewareFactory } from '../pages/trusted_apps/store/middleware'; import { eventFiltersPageMiddlewareFactory } from '../pages/event_filters/store/middleware'; type ManagementSubStateKey = keyof State[typeof MANAGEMENT_STORE_GLOBAL_NAMESPACE]; @@ -42,10 +40,7 @@ export const managementMiddlewareFactory: SecuritySubPluginMiddlewareFactory = ( createSubStateSelector(MANAGEMENT_STORE_ENDPOINTS_NAMESPACE), endpointMiddlewareFactory(coreStart, depsStart) ), - substateMiddlewareFactory( - createSubStateSelector(MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE), - trustedAppsPageMiddlewareFactory(coreStart, depsStart) - ), + substateMiddlewareFactory( createSubStateSelector(MANAGEMENT_STORE_EVENT_FILTERS_NAMESPACE), eventFiltersPageMiddlewareFactory(coreStart, depsStart) diff --git a/x-pack/plugins/security_solution/public/management/store/reducer.ts b/x-pack/plugins/security_solution/public/management/store/reducer.ts index 662d2b4322bcb..2fd20129ddca8 100644 --- a/x-pack/plugins/security_solution/public/management/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/store/reducer.ts @@ -13,15 +13,12 @@ import { import { MANAGEMENT_STORE_ENDPOINTS_NAMESPACE, MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE, - MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE, MANAGEMENT_STORE_EVENT_FILTERS_NAMESPACE, } from '../common/constants'; import { ImmutableCombineReducers } from '../../common/store'; import { Immutable } from '../../../common/endpoint/types'; import { ManagementState } from '../types'; import { endpointListReducer } from '../pages/endpoint_hosts/store/reducer'; -import { initialTrustedAppsPageState } from '../pages/trusted_apps/store/builders'; -import { trustedAppsPageReducer } from '../pages/trusted_apps/store/reducer'; import { initialEventFiltersPageState } from '../pages/event_filters/store/builders'; import { eventFiltersPageReducer } from '../pages/event_filters/store/reducer'; import { initialEndpointPageState } from '../pages/endpoint_hosts/store/builders'; @@ -34,7 +31,6 @@ const immutableCombineReducers: ImmutableCombineReducers = combineReducers; export const mockManagementState: Immutable = { [MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE]: initialPolicyDetailsState(), [MANAGEMENT_STORE_ENDPOINTS_NAMESPACE]: initialEndpointPageState(), - [MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE]: initialTrustedAppsPageState(), [MANAGEMENT_STORE_EVENT_FILTERS_NAMESPACE]: initialEventFiltersPageState(), }; @@ -44,6 +40,5 @@ export const mockManagementState: Immutable = { export const managementReducer = immutableCombineReducers({ [MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE]: policyDetailsReducer, [MANAGEMENT_STORE_ENDPOINTS_NAMESPACE]: endpointListReducer, - [MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE]: trustedAppsPageReducer, [MANAGEMENT_STORE_EVENT_FILTERS_NAMESPACE]: eventFiltersPageReducer, }); diff --git a/x-pack/plugins/security_solution/public/management/types.ts b/x-pack/plugins/security_solution/public/management/types.ts index 65063fce96e67..0ad0f2e757c00 100644 --- a/x-pack/plugins/security_solution/public/management/types.ts +++ b/x-pack/plugins/security_solution/public/management/types.ts @@ -9,7 +9,6 @@ import { CombinedState } from 'redux'; import { SecurityPageName } from '../app/types'; import { PolicyDetailsState } from './pages/policy/types'; import { EndpointState } from './pages/endpoint_hosts/types'; -import { TrustedAppsListPageState } from './pages/trusted_apps/state'; import { EventFiltersListPageState } from './pages/event_filters/types'; /** @@ -21,7 +20,6 @@ export type ManagementStoreGlobalNamespace = 'management'; export type ManagementState = CombinedState<{ policyDetails: PolicyDetailsState; endpoints: EndpointState; - trustedApps: TrustedAppsListPageState; eventFilters: EventFiltersListPageState; }>; diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/blocklist_validator.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/blocklist_validator.ts index 45d080ab956ee..5510f352a414e 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/blocklist_validator.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/blocklist_validator.ts @@ -16,7 +16,7 @@ import { } from '@kbn/lists-plugin/server'; import { BaseValidator } from './base_validator'; import { ExceptionItemLikeOptions } from '../types'; -import { isValidHash } from '../../../../common/endpoint/service/trusted_apps/validations'; +import { isValidHash } from '../../../../common/endpoint/service/artifacts/validations'; import { EndpointArtifactExceptionValidationError } from './errors'; const allowedHashes: Readonly = ['file.hash.md5', 'file.hash.sha1', 'file.hash.sha256']; diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_app_validator.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_app_validator.ts index b83e536cdee1e..af98ba7076535 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_app_validator.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_app_validator.ts @@ -19,7 +19,7 @@ import { TrustedAppConditionEntry as ConditionEntry } from '../../../../common/e import { getDuplicateFields, isValidHash, -} from '../../../../common/endpoint/service/trusted_apps/validations'; +} from '../../../../common/endpoint/service/artifacts/validations'; import { EndpointArtifactExceptionValidationError } from './errors'; const ProcessHashField = schema.oneOf([ diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 32bb864b582e4..123ce3c2a15be 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -26809,8 +26809,6 @@ "xpack.securitySolution.trustedApps.assignmentSectionDescription": "Affectez cette application de confiance globalement à toutes les politiques, ou de façon spécifique à certaines politiques.", "xpack.securitySolution.trustedapps.card.operator.is": "est", "xpack.securitySolution.trustedapps.card.operator.matches": "correspond à", - "xpack.securitySolution.trustedApps.conditionsSectionDescription": "Sélectionnez un système d'exploitation et ajoutez des conditions. La disponibilité des conditions peut dépendre de votre système d’exploitation.", - "xpack.securitySolution.trustedApps.conditionsSectionTitle": "Conditions", "xpack.securitySolution.trustedapps.create.conditionFieldDegradedPerformanceMsg": "[{row}] L'utilisation d'un caractère générique dans le nom de fichier affectera les performances du point de terminaison", "xpack.securitySolution.trustedapps.create.conditionFieldDuplicatedMsg": "{field} ne peut pas être ajouté plus d'une fois", "xpack.securitySolution.trustedapps.create.conditionFieldInvalidHashMsg": "[{row}] Valeur de hachage non valide", @@ -26818,41 +26816,10 @@ "xpack.securitySolution.trustedapps.create.conditionFieldValueRequiredMsg": "[{row}] L'entrée de champ doit inclure une valeur", "xpack.securitySolution.trustedapps.create.conditionRequiredMsg": "Au moins une définition de champ est requise", "xpack.securitySolution.trustedapps.create.description": "Description", - "xpack.securitySolution.trustedapps.create.name": "Nommer votre application de confiance", "xpack.securitySolution.trustedapps.create.nameRequiredMsg": "Le nom est requis", - "xpack.securitySolution.trustedapps.create.os": "Sélectionner un système d'exploitation", "xpack.securitySolution.trustedapps.create.osRequiredMsg": "Le système d'exploitation est requis", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.cancelButton": "Annuler", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.createSaveButton": "Ajouter une application de confiance", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.createTitle": "Ajouter une application de confiance", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.editSaveButton": "Enregistrer", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.editTitle": "Modifier une application de confiance", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.expiredLicenseMessage": "Votre licence Kibana est passée à une version inférieure. Les futures configurations de politiques seront désormais globalement affectées à toutes les politiques. Pour en savoir plus, consultez notre ", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.expiredLicenseTitle": "Licence expirée", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.notFoundToastMessage": "Impossible de modifier l'application de confiance ({apiMsg})", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.successToastTitle": "\"{name}\" a été ajouté à la liste d'applications de confiance.", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.updateSuccessToastTitle": "\"{name}\" a été mis à jour.", - "xpack.securitySolution.trustedapps.creationSuccess.title": "Cette action a réussi !", - "xpack.securitySolution.trustedapps.deletionDialog.calloutMessage": "La suppression de cette entrée entraînera son retrait dans {count} {count, plural, one {politique associée} other {politiques associées}}.", - "xpack.securitySolution.trustedapps.deletionDialog.calloutTitle": "Avertissement", - "xpack.securitySolution.trustedapps.deletionDialog.cancelButton": "Annuler", - "xpack.securitySolution.trustedapps.deletionDialog.confirmButton": "Supprimer", - "xpack.securitySolution.trustedapps.deletionDialog.subMessage": "Cette action ne peut pas être annulée. Voulez-vous vraiment continuer ?", - "xpack.securitySolution.trustedapps.deletionDialog.title": "Supprimer \"{name}\"", - "xpack.securitySolution.trustedapps.deletionError.text": "Impossible de retirer \"{name}\" de la liste d'applications de confiance. Raison : {message}", - "xpack.securitySolution.trustedapps.deletionError.title": "Échec du retrait", - "xpack.securitySolution.trustedapps.deletionSuccess.text": "\"{name}\" a été retiré de la liste d'applications de confiance.", - "xpack.securitySolution.trustedapps.deletionSuccess.title": "Retrait effectué avec succès", - "xpack.securitySolution.trustedApps.detailsSectionTitle": "Détails", - "xpack.securitySolution.trustedapps.docsLink": "Documentation relative aux applications de confiance.", - "xpack.securitySolution.trustedapps.grid.cardAction.delete": "Supprimer une application de confiance", - "xpack.securitySolution.trustedapps.grid.cardAction.edit": "Modifier une application de confiance", - "xpack.securitySolution.trustedapps.grid.policyDetailsLinkBackLabel": "Retour aux applications de confiance", "xpack.securitySolution.trustedapps.list.addButton": "Ajouter une application de confiance", - "xpack.securitySolution.trustedapps.list.pageTitle": "Applications de confiance", - "xpack.securitySolution.trustedapps.list.search.placeholder": "Rechercher sur les champs ci-dessous : nom, description, valeur", - "xpack.securitySolution.trustedapps.list.totalCount": "Affichage de {totalItemsCount, plural, one {# application de confiance} other {# applications de confiance}}", - "xpack.securitySolution.trustedapps.listEmptyState.message": "Ajoutez une application de confiance pour améliorer les performances ou réduire les conflits avec d'autres applications en cours d'exécution sur vos hôtes.", + "xpack.securitySolution.trustedapps.listEmptyState.message": "Aucune application de confiance ne figure actuellement dans votre point de terminaison.", "xpack.securitySolution.trustedapps.listEmptyState.title": "Ajouter votre première application de confiance", "xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field.description.hash": "md5, sha1 ou sha256", "xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field.description.path": "Chemin complet de l'application", @@ -26863,14 +26830,9 @@ "xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.removeLabel": "Retirer l'entrée", "xpack.securitySolution.trustedapps.logicalConditionBuilder.group.andOperator": "AND", "xpack.securitySolution.trustedapps.logicalConditionBuilder.noEntries": "Aucune condition définie", - "xpack.securitySolution.trustedapps.middleware.editIdMissing": "Aucun ID fourni", "xpack.securitySolution.trustedapps.trustedapp.entry.field": "Champ", "xpack.securitySolution.trustedapps.trustedapp.entry.operator": "Opérateur", "xpack.securitySolution.trustedapps.trustedapp.entry.value": "Valeur", - "xpack.securitySolution.trustedapps.updateSuccess.title": "Cette action a réussi !", - "xpack.securitySolution.trustedapps.view.toggle.grid": "Vue Grille", - "xpack.securitySolution.trustedapps.view.toggle.list": "Vue Liste", - "xpack.securitySolution.trustedapps.viewTypeToggle.controlLegend": "Type de vue", "xpack.securitySolution.trustedAppsTab": "Applications de confiance", "xpack.securitySolution.uiSettings.defaultAnomalyScoreDescription": "

Valeur au-dessus de laquelle les anomalies de tâche de Machine Learning sont affichées dans l'application Security.

Valeurs valides : 0 à 100.

", "xpack.securitySolution.uiSettings.defaultAnomalyScoreLabel": "Seuil d'anomalie", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index e79baef772910..cd6ac0cdfc14d 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -26983,8 +26983,6 @@ "xpack.securitySolution.trustedApps.assignmentSectionDescription": "すべてのポリシーでグローバルにこの信頼できるアプリケーションを割り当てるか、特定のポリシーに割り当てます。", "xpack.securitySolution.trustedapps.card.operator.is": "is", "xpack.securitySolution.trustedapps.card.operator.matches": "一致", - "xpack.securitySolution.trustedApps.conditionsSectionDescription": "オペレーティングシステムを選択して、条件を追加します。条件の可用性は選択したOSによって異なる場合があります。", - "xpack.securitySolution.trustedApps.conditionsSectionTitle": "条件", "xpack.securitySolution.trustedapps.create.conditionFieldDegradedPerformanceMsg": "[{row}] ファイル名のワイルドカードはエンドポイントのパフォーマンスに影響します", "xpack.securitySolution.trustedapps.create.conditionFieldDuplicatedMsg": "{field}を複数回追加できません", "xpack.securitySolution.trustedapps.create.conditionFieldInvalidHashMsg": "[{row}] 無効なハッシュ値", @@ -26992,39 +26990,9 @@ "xpack.securitySolution.trustedapps.create.conditionFieldValueRequiredMsg": "[{row}] フィールドエントリには値が必要です", "xpack.securitySolution.trustedapps.create.conditionRequiredMsg": "1つ以上のフィールド定義が必要です", "xpack.securitySolution.trustedapps.create.description": "説明", - "xpack.securitySolution.trustedapps.create.name": "信頼できるアプリケーションに名前を付ける", "xpack.securitySolution.trustedapps.create.nameRequiredMsg": "名前が必要です", - "xpack.securitySolution.trustedapps.create.os": "オペレーティングシステムを選択", "xpack.securitySolution.trustedapps.create.osRequiredMsg": "オペレーティングシステムは必須です", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.cancelButton": "キャンセル", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.createSaveButton": "信頼できるアプリケーションを追加", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.createTitle": "信頼できるアプリケーションを追加", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.editSaveButton": "保存", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.editTitle": "信頼できるアプリケーションを編集", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.expiredLicenseMessage": "Kibanaライセンスがダウングレードされました。今後のポリシー構成はグローバルにすべてのポリシーに割り当てられます。詳細はご覧ください。 ", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.expiredLicenseTitle": "失効したライセンス", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.notFoundToastMessage": "信頼できるアプリケーションを編集できません({apiMsg})", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.successToastTitle": "\"{name}\"は信頼できるアプリケーションリストに追加されました。", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.updateSuccessToastTitle": "\"{name}\"が更新されました。", - "xpack.securitySolution.trustedapps.creationSuccess.title": "成功!", - "xpack.securitySolution.trustedapps.deletionDialog.calloutMessage": "このエントリを削除すると、{count}個の関連付けられた{count, plural, other {ポリシー}}から削除されます。", - "xpack.securitySolution.trustedapps.deletionDialog.calloutTitle": "警告", - "xpack.securitySolution.trustedapps.deletionDialog.cancelButton": "キャンセル", - "xpack.securitySolution.trustedapps.deletionDialog.confirmButton": "削除", - "xpack.securitySolution.trustedapps.deletionDialog.subMessage": "この操作は元に戻すことができません。続行していいですか?", - "xpack.securitySolution.trustedapps.deletionDialog.title": "\"{name}\"を削除", - "xpack.securitySolution.trustedapps.deletionError.text": "信頼できるアプリケーションリストから\"{name}\"を削除できません。理由:{message}", - "xpack.securitySolution.trustedapps.deletionError.title": "削除失敗", - "xpack.securitySolution.trustedapps.deletionSuccess.text": "\"{name}\"は信頼できるアプリケーションリストから削除されました。", - "xpack.securitySolution.trustedapps.deletionSuccess.title": "正常に削除されました", - "xpack.securitySolution.trustedApps.detailsSectionTitle": "詳細", - "xpack.securitySolution.trustedapps.docsLink": "信頼できるアプリケーションドキュメント。", - "xpack.securitySolution.trustedapps.grid.cardAction.delete": "信頼できるアプリケーションを削除", - "xpack.securitySolution.trustedapps.grid.cardAction.edit": "信頼できるアプリケーションを編集", - "xpack.securitySolution.trustedapps.grid.policyDetailsLinkBackLabel": "信頼できるアプリケーションに戻る", "xpack.securitySolution.trustedapps.list.addButton": "信頼できるアプリケーションを追加", - "xpack.securitySolution.trustedapps.list.pageTitle": "信頼できるアプリケーション", - "xpack.securitySolution.trustedapps.list.search.placeholder": "次のフィールドで検索:名前、説明、値", "xpack.securitySolution.trustedapps.listEmptyState.message": "パフォーマンスを改善したり、ホストで実行されている他のアプリケーションとの競合を解消したりするには、信頼できるアプリケーションを追加します。", "xpack.securitySolution.trustedapps.listEmptyState.title": "最初の信頼できるアプリケーションを追加", "xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field.description.hash": "md5、sha1、sha256", @@ -27036,14 +27004,9 @@ "xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.removeLabel": "エントリを削除", "xpack.securitySolution.trustedapps.logicalConditionBuilder.group.andOperator": "AND", "xpack.securitySolution.trustedapps.logicalConditionBuilder.noEntries": "条件が定義されていません", - "xpack.securitySolution.trustedapps.middleware.editIdMissing": "IDが指定されていません", "xpack.securitySolution.trustedapps.trustedapp.entry.field": "フィールド", "xpack.securitySolution.trustedapps.trustedapp.entry.operator": "演算子", "xpack.securitySolution.trustedapps.trustedapp.entry.value": "値", - "xpack.securitySolution.trustedapps.updateSuccess.title": "成功!", - "xpack.securitySolution.trustedapps.view.toggle.grid": "グリッドビュー", - "xpack.securitySolution.trustedapps.view.toggle.list": "リストビュー", - "xpack.securitySolution.trustedapps.viewTypeToggle.controlLegend": "ビュータイプ", "xpack.securitySolution.trustedAppsTab": "信頼できるアプリケーション", "xpack.securitySolution.uiSettings.defaultAnomalyScoreDescription": "

機械学習ジョブの異常がこの値を超えると、セキュリティアプリに表示されます。

有効な値:0 ~ 100。

", "xpack.securitySolution.uiSettings.defaultAnomalyScoreLabel": "デフォルトの異常しきい値", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 19e216f111a62..5fe8340d34682 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -27017,8 +27017,6 @@ "xpack.securitySolution.trustedApps.assignmentSectionDescription": "跨所有策略全局分配此受信任的应用程序,或将其分配给特定策略。", "xpack.securitySolution.trustedapps.card.operator.is": "是", "xpack.securitySolution.trustedapps.card.operator.matches": "匹配", - "xpack.securitySolution.trustedApps.conditionsSectionDescription": "选择操作系统,然后添加条件。条件的可用性可能取决于您选定的 OS。", - "xpack.securitySolution.trustedApps.conditionsSectionTitle": "条件", "xpack.securitySolution.trustedapps.create.conditionFieldDegradedPerformanceMsg": "[{row}] 文件名中存在通配符将影响终端性能", "xpack.securitySolution.trustedapps.create.conditionFieldDuplicatedMsg": "不能多次添加 {field}", "xpack.securitySolution.trustedapps.create.conditionFieldInvalidHashMsg": "[{row}] 无效的哈希值", @@ -27026,40 +27024,9 @@ "xpack.securitySolution.trustedapps.create.conditionFieldValueRequiredMsg": "[{row}] 字段条目必须包含值", "xpack.securitySolution.trustedapps.create.conditionRequiredMsg": "至少需要一个字段定义", "xpack.securitySolution.trustedapps.create.description": "描述", - "xpack.securitySolution.trustedapps.create.name": "命名受信任的应用程序", "xpack.securitySolution.trustedapps.create.nameRequiredMsg": "“名称”必填", - "xpack.securitySolution.trustedapps.create.os": "选择操作系统", "xpack.securitySolution.trustedapps.create.osRequiredMsg": "“操作系统”必填", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.cancelButton": "取消", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.createSaveButton": "添加受信任的应用程序", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.createTitle": "添加受信任的应用程序", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.editSaveButton": "保存", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.editTitle": "编辑受信任的应用程序", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.expiredLicenseMessage": "您的 Kibana 许可证已降级。现在会将未来的策略配置全局分配给所有策略。有关更多信息,请参见 ", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.expiredLicenseTitle": "已过期许可证", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.notFoundToastMessage": "无法编辑受信任的应用程序 ({apiMsg})", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.successToastTitle": "“{name}”已添加到受信任的应用程序列表。", - "xpack.securitySolution.trustedapps.createTrustedAppFlyout.updateSuccessToastTitle": "“{name}”已更新。", - "xpack.securitySolution.trustedapps.creationSuccess.title": "成功!", - "xpack.securitySolution.trustedapps.deletionDialog.calloutMessage": "删除此条目会将其从 {count} 个关联{count, plural, other {策略}}中移除。", - "xpack.securitySolution.trustedapps.deletionDialog.calloutTitle": "警告", - "xpack.securitySolution.trustedapps.deletionDialog.cancelButton": "取消", - "xpack.securitySolution.trustedapps.deletionDialog.confirmButton": "删除", - "xpack.securitySolution.trustedapps.deletionDialog.subMessage": "此操作无法撤消。是否确定要继续?", - "xpack.securitySolution.trustedapps.deletionDialog.title": "删除“{name}”", - "xpack.securitySolution.trustedapps.deletionError.text": "无法从受信任的应用程序列表中移除“{name}”。原因:{message}", - "xpack.securitySolution.trustedapps.deletionError.title": "移除失败", - "xpack.securitySolution.trustedapps.deletionSuccess.text": "“{name}”已从受信任的应用程序列表中移除。", - "xpack.securitySolution.trustedapps.deletionSuccess.title": "已成功移除", - "xpack.securitySolution.trustedApps.detailsSectionTitle": "详情", - "xpack.securitySolution.trustedapps.docsLink": "受信任的应用程序文档。", - "xpack.securitySolution.trustedapps.grid.cardAction.delete": "删除受信任的应用程序", - "xpack.securitySolution.trustedapps.grid.cardAction.edit": "编辑受信任的应用程序", - "xpack.securitySolution.trustedapps.grid.policyDetailsLinkBackLabel": "返回到受信任的应用程序", "xpack.securitySolution.trustedapps.list.addButton": "添加受信任的应用程序", - "xpack.securitySolution.trustedapps.list.pageTitle": "受信任的应用程序", - "xpack.securitySolution.trustedapps.list.search.placeholder": "搜索下面的字段:name、description、value", - "xpack.securitySolution.trustedapps.list.totalCount": "正在显示 {totalItemsCount, plural, other {# 个受信任的应用程序}}", "xpack.securitySolution.trustedapps.listEmptyState.message": "添加受信任的应用程序,以提高性能或缓解与主机上运行的其他应用程序的冲突。", "xpack.securitySolution.trustedapps.listEmptyState.title": "添加您的首个受信任应用程序", "xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field.description.hash": "md5、sha1 或 sha256", @@ -27071,14 +27038,9 @@ "xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.removeLabel": "移除条目", "xpack.securitySolution.trustedapps.logicalConditionBuilder.group.andOperator": "AND", "xpack.securitySolution.trustedapps.logicalConditionBuilder.noEntries": "未定义条件", - "xpack.securitySolution.trustedapps.middleware.editIdMissing": "未提供 ID", "xpack.securitySolution.trustedapps.trustedapp.entry.field": "字段", "xpack.securitySolution.trustedapps.trustedapp.entry.operator": "运算符", "xpack.securitySolution.trustedapps.trustedapp.entry.value": "值", - "xpack.securitySolution.trustedapps.updateSuccess.title": "成功!", - "xpack.securitySolution.trustedapps.view.toggle.grid": "网格视图", - "xpack.securitySolution.trustedapps.view.toggle.list": "列表视图", - "xpack.securitySolution.trustedapps.viewTypeToggle.controlLegend": "视图类型", "xpack.securitySolution.trustedAppsTab": "受信任的应用程序", "xpack.securitySolution.uiSettings.defaultAnomalyScoreDescription": "

要在 Security 应用中显示的 Machine Learning 作业异常所需超过的值。

有效值:0 到 100。

", "xpack.securitySolution.uiSettings.defaultAnomalyScoreLabel": "异常阈值", diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/fleet_integrations.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/fleet_integrations.ts index 4f9b7ad7c0401..ea5746060dedc 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/fleet_integrations.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/fleet_integrations.ts @@ -48,7 +48,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('should show the Trusted Apps page when link is clicked', async () => { await (await fleetIntegrations.findIntegrationDetailCustomTab()).click(); await (await testSubjects.find('trustedApps-artifactsLink')).click(); - await trustedApps.ensureIsOnTrustedAppsListPage(); + await trustedApps.ensureIsOnTrustedAppsEmptyPage(); }); }); }); diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/trusted_apps_list.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/trusted_apps_list.ts index 7f2357cebb2c6..4643b91303be0 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/trusted_apps_list.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/trusted_apps_list.ts @@ -37,18 +37,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const SHA256 = 'A4370C0CF81686C0B696FA6261c9d3e0d810ae704ab8301839dffd5d5112f476'; // Add it - await testSubjects.click('trustedAppsListAddButton'); - await testSubjects.setValue( - 'addTrustedAppFlyout-createForm-nameTextField', - 'Windows Defender' - ); - await testSubjects.setValue( - 'addTrustedAppFlyout-createForm-conditionsBuilder-group1-entry0-value', - SHA256 - ); - await testSubjects.click('addTrustedAppFlyout-createButton'); + await testSubjects.click('trustedAppsListPage-emptyState-addButton'); + await testSubjects.setValue('trustedApps-form-nameTextField', 'Windows Defender'); + await testSubjects.setValue('trustedApps-form-conditionsBuilder-group1-entry0-value', SHA256); + await testSubjects.click('trustedAppsListPage-flyout-submitButton'); expect( - await testSubjects.getVisibleText('trustedAppCard-criteriaConditions-condition') + await testSubjects.getVisibleText('trustedAppsListPage-card-criteriaConditions-condition') ).to.equal( 'AND process.hash.*IS a4370c0cf81686c0b696fa6261c9d3e0d810ae704ab8301839dffd5d5112f476' ); @@ -61,11 +55,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { // Remove it await pageObjects.trustedApps.clickCardActionMenu(); - await testSubjects.click('deleteTrustedAppAction'); - await testSubjects.click('trustedAppDeletionConfirm'); - await testSubjects.waitForDeleted('trustedAppDeletionConfirm'); + await testSubjects.click('trustedAppsListPage-card-cardDeleteAction'); + await testSubjects.click('trustedAppsListPage-deleteModal-submitButton'); + await testSubjects.waitForDeleted('trustedAppsListPage-deleteModal-submitButton'); // We only expect one trusted app to have been visible - await testSubjects.missingOrFail('trustedAppCard'); + await testSubjects.missingOrFail('trustedAppsListPage-card'); // Header has gone because there is no trusted app await testSubjects.missingOrFail('header-page-title'); }); diff --git a/x-pack/test/security_solution_endpoint/page_objects/trusted_apps_page.ts b/x-pack/test/security_solution_endpoint/page_objects/trusted_apps_page.ts index 1678358acc11e..7dc2e84358f54 100644 --- a/x-pack/test/security_solution_endpoint/page_objects/trusted_apps_page.ts +++ b/x-pack/test/security_solution_endpoint/page_objects/trusted_apps_page.ts @@ -24,7 +24,11 @@ export function TrustedAppsPageProvider({ getService, getPageObjects }: FtrProvi * ensures that the Policy Page is the currently display view */ async ensureIsOnTrustedAppsListPage() { - await testSubjects.existOrFail('trustedAppsListPage'); + await testSubjects.existOrFail('trustedAppsListPage-list'); + }, + + async ensureIsOnTrustedAppsEmptyPage() { + await testSubjects.existOrFail('trustedAppsListPage-emptyState'); }, /** @@ -41,7 +45,7 @@ export function TrustedAppsPageProvider({ getService, getPageObjects }: FtrProvi */ async clickCardActionMenu() { await this.ensureIsOnTrustedAppsListPage(); - await testSubjects.click('trustedAppCard-header-actions-button'); + await testSubjects.click('trustedAppsListPage-card-header-actions-button'); }, }; }