mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[8.0] [Security Solution][Endpoint] Move the UI Trusted Apps API calls to use the Exceptions List Items APIs (#118801) (#119632)
* [Security Solution][Endpoint] Move the UI Trusted Apps API calls to use the Exceptions List Items APIs (#118801) * renamed TA service file to match name and added exports to index.ts * Move `EndpointError` to top-level `common/endpoint/errors` * add validation framework to TA service * Mappers + Create service method changed to use Exceptions API * Update Trusted app service method moved to use Exceptions API * new generators # Conflicts: # x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.test.tsx # x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/index.ts # x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx * Fix import issue due to PR that was not backported to 8.0
This commit is contained in:
parent
16f52941b8
commit
2d4cc5e18c
36 changed files with 1335 additions and 586 deletions
|
@ -11,6 +11,7 @@ import * as t from 'io-ts';
|
|||
export const exceptionListType = t.keyof({
|
||||
detection: null,
|
||||
endpoint: null,
|
||||
endpoint_trusted_apps: null,
|
||||
endpoint_events: null,
|
||||
endpoint_host_isolation_exceptions: null,
|
||||
});
|
||||
|
@ -20,6 +21,7 @@ export type ExceptionListTypeOrUndefined = t.TypeOf<typeof exceptionListTypeOrUn
|
|||
export enum ExceptionListTypeEnum {
|
||||
DETECTION = 'detection',
|
||||
ENDPOINT = 'endpoint',
|
||||
ENDPOINT_TRUSTED_APPS = 'endpoint',
|
||||
ENDPOINT_EVENTS = 'endpoint_events',
|
||||
ENDPOINT_HOST_ISOLATION_EXCEPTIONS = 'endpoint_host_isolation_exceptions',
|
||||
}
|
||||
|
|
|
@ -86,7 +86,7 @@ describe('Lists', () => {
|
|||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "1" supplied to "Array<{| id: NonEmptyString, list_id: NonEmptyString, type: "detection" | "endpoint" | "endpoint_events" | "endpoint_host_isolation_exceptions", namespace_type: "agnostic" | "single" |}>"',
|
||||
'Invalid value "1" supplied to "Array<{| id: NonEmptyString, list_id: NonEmptyString, type: "detection" | "endpoint" | "endpoint_trusted_apps" | "endpoint_events" | "endpoint_host_isolation_exceptions", namespace_type: "agnostic" | "single" |}>"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
@ -117,8 +117,8 @@ describe('Lists', () => {
|
|||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "1" supplied to "(Array<{| id: NonEmptyString, list_id: NonEmptyString, type: "detection" | "endpoint" | "endpoint_events" | "endpoint_host_isolation_exceptions", namespace_type: "agnostic" | "single" |}> | undefined)"',
|
||||
'Invalid value "[1]" supplied to "(Array<{| id: NonEmptyString, list_id: NonEmptyString, type: "detection" | "endpoint" | "endpoint_events" | "endpoint_host_isolation_exceptions", namespace_type: "agnostic" | "single" |}> | undefined)"',
|
||||
'Invalid value "1" supplied to "(Array<{| id: NonEmptyString, list_id: NonEmptyString, type: "detection" | "endpoint" | "endpoint_trusted_apps" | "endpoint_events" | "endpoint_host_isolation_exceptions", namespace_type: "agnostic" | "single" |}> | undefined)"',
|
||||
'Invalid value "[1]" supplied to "(Array<{| id: NonEmptyString, list_id: NonEmptyString, type: "detection" | "endpoint" | "endpoint_trusted_apps" | "endpoint_events" | "endpoint_host_isolation_exceptions", namespace_type: "agnostic" | "single" |}> | undefined)"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { BaseDataGenerator } from './base_data_generator';
|
||||
import { POLICY_REFERENCE_PREFIX } from '../service/trusted_apps/mapping';
|
||||
import { ConditionEntryField } from '../types';
|
||||
|
||||
export class ExceptionsListItemGenerator extends BaseDataGenerator<ExceptionListItemSchema> {
|
||||
generate(overrides: Partial<ExceptionListItemSchema> = {}): ExceptionListItemSchema {
|
||||
return {
|
||||
_version: this.randomString(5),
|
||||
comments: [],
|
||||
created_at: this.randomPastDate(),
|
||||
created_by: this.randomUser(),
|
||||
description: 'created by ExceptionListItemGenerator',
|
||||
entries: [
|
||||
{
|
||||
field: ConditionEntryField.HASH,
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
value: '1234234659af249ddf3e40864e9fb241',
|
||||
},
|
||||
{
|
||||
field: ConditionEntryField.PATH,
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
value: '/one/two/three',
|
||||
},
|
||||
],
|
||||
id: this.seededUUIDv4(),
|
||||
item_id: this.seededUUIDv4(),
|
||||
list_id: 'endpoint_list_id',
|
||||
meta: {},
|
||||
name: `Generated Exception (${this.randomString(5)})`,
|
||||
namespace_type: 'agnostic',
|
||||
os_types: [this.randomOSFamily()] as ExceptionListItemSchema['os_types'],
|
||||
tags: [`${POLICY_REFERENCE_PREFIX}all`],
|
||||
tie_breaker_id: this.seededUUIDv4(),
|
||||
type: 'simple',
|
||||
updated_at: '2020-04-20T15:25:31.830Z',
|
||||
updated_by: this.randomUser(),
|
||||
...(overrides || {}),
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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 { BaseDataGenerator } from './base_data_generator';
|
||||
import { agentPolicyStatuses, GetAgentPoliciesResponseItem } from '../../../../fleet/common';
|
||||
|
||||
export class FleetAgentPolicyGenerator extends BaseDataGenerator<GetAgentPoliciesResponseItem> {
|
||||
generate(overrides: Partial<GetAgentPoliciesResponseItem> = {}): GetAgentPoliciesResponseItem {
|
||||
return {
|
||||
id: this.seededUUIDv4(),
|
||||
name: `Agent Policy ${this.randomString(4)}`,
|
||||
status: agentPolicyStatuses.Active,
|
||||
description: 'Created by FleetAgentPolicyGenerator',
|
||||
namespace: 'default',
|
||||
is_managed: false,
|
||||
monitoring_enabled: ['logs', 'metrics'],
|
||||
revision: 2,
|
||||
updated_at: '2020-07-22T16:36:49.196Z',
|
||||
updated_by: this.randomUser(),
|
||||
package_policies: ['852491f0-cc39-11ea-bac2-cdbf95b4b41a'],
|
||||
agents: 0,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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 { BaseDataGenerator } from './base_data_generator';
|
||||
import { PackagePolicy } from '../../../../fleet/common';
|
||||
import { policyFactory } from '../models/policy_config';
|
||||
import { PolicyData } from '../types';
|
||||
|
||||
type PartialPackagePolicy = Partial<Omit<PackagePolicy, 'inputs'>> & {
|
||||
inputs?: PackagePolicy['inputs'];
|
||||
};
|
||||
|
||||
type PartialEndpointPolicyData = Partial<Omit<PolicyData, 'inputs'>> & {
|
||||
inputs?: PolicyData['inputs'];
|
||||
};
|
||||
|
||||
export class FleetPackagePolicyGenerator extends BaseDataGenerator<PackagePolicy> {
|
||||
generate(overrides: PartialPackagePolicy = {}): PackagePolicy {
|
||||
return {
|
||||
id: this.seededUUIDv4(),
|
||||
name: `Package Policy {${this.randomString(4)})`,
|
||||
description: 'Policy to protect the worlds data',
|
||||
created_at: this.randomPastDate(),
|
||||
created_by: this.randomUser(),
|
||||
updated_at: new Date().toISOString(),
|
||||
updated_by: this.randomUser(),
|
||||
policy_id: this.seededUUIDv4(), // agent policy id
|
||||
enabled: true,
|
||||
output_id: '',
|
||||
inputs: [],
|
||||
namespace: 'default',
|
||||
package: {
|
||||
name: 'endpoint',
|
||||
title: 'Elastic Endpoint',
|
||||
version: '1.0.0',
|
||||
},
|
||||
revision: 1,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
generateEndpointPackagePolicy(overrides: PartialEndpointPolicyData = {}): PolicyData {
|
||||
return {
|
||||
...this.generate({
|
||||
name: `Endpoint Policy {${this.randomString(4)})`,
|
||||
}),
|
||||
inputs: [
|
||||
{
|
||||
type: 'endpoint',
|
||||
enabled: true,
|
||||
streams: [],
|
||||
config: {
|
||||
artifact_manifest: {
|
||||
value: {
|
||||
manifest_version: '1.0.0',
|
||||
schema_version: 'v1',
|
||||
artifacts: {},
|
||||
},
|
||||
},
|
||||
policy: {
|
||||
value: policyFactory(),
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
}
|
18
x-pack/plugins/security_solution/common/endpoint/errors.ts
Normal file
18
x-pack/plugins/security_solution/common/endpoint/errors.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Endpoint base error class that supports an optional second argument for providing additional data
|
||||
* for the error.
|
||||
*/
|
||||
export class EndpointError<MetaType = unknown> extends Error {
|
||||
constructor(message: string, public readonly meta?: MetaType) {
|
||||
super(message);
|
||||
// For debugging - capture name of subclasses
|
||||
this.name = this.constructor.name;
|
||||
}
|
||||
}
|
|
@ -1535,6 +1535,7 @@ export class EndpointDocGenerator extends BaseDataGenerator {
|
|||
*/
|
||||
public generatePolicyPackagePolicy(): PolicyData {
|
||||
const created = new Date(Date.now() - 8.64e7).toISOString(); // 24h ago
|
||||
// FIXME: remove and use new FleetPackagePolicyGenerator (#2262)
|
||||
return {
|
||||
id: this.seededUUIDv4(),
|
||||
name: 'Endpoint Policy',
|
||||
|
@ -1579,6 +1580,7 @@ export class EndpointDocGenerator extends BaseDataGenerator {
|
|||
* Generate an Agent Policy (ingest)
|
||||
*/
|
||||
public generateAgentPolicy(): GetAgentPoliciesResponseItem {
|
||||
// FIXME: remove and use new FleetPackagePolicyGenerator (#2262)
|
||||
return {
|
||||
id: this.seededUUIDv4(),
|
||||
name: 'Agent Policy',
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { fromKueryExpression } from '@kbn/es-query';
|
||||
import {
|
||||
httpHandlerMockFactory,
|
||||
ResponseProvidersInterface,
|
||||
|
@ -12,6 +13,7 @@ import {
|
|||
import {
|
||||
AGENT_API_ROUTES,
|
||||
AGENT_POLICY_API_ROUTES,
|
||||
AGENT_POLICY_SAVED_OBJECT_TYPE,
|
||||
appRoutesService,
|
||||
CheckPermissionsResponse,
|
||||
EPM_API_ROUTES,
|
||||
|
@ -22,6 +24,97 @@ import {
|
|||
} from '../../../../../fleet/common';
|
||||
import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data';
|
||||
import { GetPolicyListResponse, GetPolicyResponse } from '../policy/types';
|
||||
import { FleetAgentPolicyGenerator } from '../../../../common/endpoint/data_generators/fleet_agent_policy_generator';
|
||||
|
||||
interface KqlArgumentType {
|
||||
type: string;
|
||||
value?: string | boolean;
|
||||
function?: string;
|
||||
arguments?: KqlArgumentType[];
|
||||
}
|
||||
|
||||
const getPackagePoliciesFromKueryString = (kueryString: string): string[] => {
|
||||
if (!kueryString) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const kueryAst: ReturnType<typeof fromKueryExpression> & {
|
||||
arguments?: KqlArgumentType[];
|
||||
} = fromKueryExpression(kueryString);
|
||||
|
||||
/**
|
||||
* # ABOUT THE STRUCTURE RETURNED BY THE KQL PARSER:
|
||||
*
|
||||
* The kuery AST has a structure similar to to this:
|
||||
* given string:
|
||||
*
|
||||
* ingest-agent-policies.package_policies: (ddf6570b-9175-4a6d-b288-61a09771c647 or b8e616ae-44fc-4be7-846c-ce8fa5c082dd or 2d95bec3-b48f-4db7-9622-a2b061cc031d)
|
||||
*
|
||||
* output would be:
|
||||
* {
|
||||
* "type": "function",
|
||||
* "function": "or", // this would not be here if no `OR` was found in the string
|
||||
* "arguments": [
|
||||
* {
|
||||
* "type": "function",
|
||||
* "function": "is",
|
||||
* "arguments": [
|
||||
* {
|
||||
* "type": "literal",
|
||||
* "value": "ingest-agent-policies.package_policies"
|
||||
* },
|
||||
* {
|
||||
* "type": "literal",
|
||||
* "value": "ddf6570b-9175-4a6d-b288-61a09771c647"
|
||||
* },
|
||||
* {
|
||||
* "type": "literal",
|
||||
* "value": false
|
||||
* }
|
||||
* ]
|
||||
* },
|
||||
* // .... other kquery arguments here
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
|
||||
// Because there could be be many combinations of OR/AND, we just look for any defined literal that
|
||||
// looks ot have a value for package_policies.
|
||||
if (kueryAst.arguments) {
|
||||
const packagePolicyIds: string[] = [];
|
||||
const kqlArgumentQueue = [...kueryAst.arguments];
|
||||
|
||||
while (kqlArgumentQueue.length > 0) {
|
||||
const kqlArgument = kqlArgumentQueue.shift();
|
||||
|
||||
if (kqlArgument) {
|
||||
if (kqlArgument.arguments) {
|
||||
kqlArgumentQueue.push(...kqlArgument.arguments);
|
||||
}
|
||||
|
||||
if (
|
||||
kqlArgument.type === 'literal' &&
|
||||
kqlArgument.value === `${AGENT_POLICY_SAVED_OBJECT_TYPE}.package_policies`
|
||||
) {
|
||||
// If the next argument looks to be a value, then user it
|
||||
const nextArgument = kqlArgumentQueue[0];
|
||||
if (
|
||||
nextArgument &&
|
||||
nextArgument.type === 'literal' &&
|
||||
'string' === typeof nextArgument.value
|
||||
) {
|
||||
packagePolicyIds.push(nextArgument.value);
|
||||
kqlArgumentQueue.shift();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return packagePolicyIds;
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
export type FleetGetPackageListHttpMockInterface = ResponseProvidersInterface<{
|
||||
packageList: () => GetPackagesResponse;
|
||||
|
@ -70,6 +163,7 @@ export const fleetGetEndpointPackagePolicyListHttpMock =
|
|||
path: PACKAGE_POLICY_API_ROUTES.LIST_PATTERN,
|
||||
method: 'get',
|
||||
handler: () => {
|
||||
// FIXME: use new FleetPackagePolicyGenerator (#2262)
|
||||
const generator = new EndpointDocGenerator('seed');
|
||||
|
||||
const items = Array.from({ length: 5 }, (_, index) => {
|
||||
|
@ -97,23 +191,37 @@ export const fleetGetAgentPolicyListHttpMock =
|
|||
id: 'agentPolicy',
|
||||
path: AGENT_POLICY_API_ROUTES.LIST_PATTERN,
|
||||
method: 'get',
|
||||
handler: () => {
|
||||
handler: ({ query }) => {
|
||||
const generator = new EndpointDocGenerator('seed');
|
||||
const agentPolicyGenerator = new FleetAgentPolicyGenerator('seed');
|
||||
const endpointMetadata = generator.generateHostMetadata();
|
||||
const agentPolicy = generator.generateAgentPolicy();
|
||||
const requiredPolicyIds: string[] = [
|
||||
// Make sure that the Agent policy returned from the API has the Integration Policy ID that
|
||||
// the first endpoint metadata generated is using. This is needed especially when testing the
|
||||
// Endpoint Details flyout where certain actions might be disabled if we know the endpoint integration policy no
|
||||
// longer exists.
|
||||
endpointMetadata.Endpoint.policy.applied.id,
|
||||
|
||||
// Make sure that the Agent policy returned from the API has the Integration Policy ID that
|
||||
// the endpoint metadata is using. This is needed especially when testing the Endpoint Details
|
||||
// flyout where certain actions might be disabled if we know the endpoint integration policy no
|
||||
// longer exists.
|
||||
(agentPolicy.package_policies as string[]).push(
|
||||
endpointMetadata.Endpoint.policy.applied.id
|
||||
);
|
||||
// In addition, some of our UI logic looks for the existence of certain Endpoint Integration policies
|
||||
// using the Agents Policy API (normally when checking IDs since query by ids is not supported via API)
|
||||
// so also add the first two package policy IDs that the `fleetGetEndpointPackagePolicyListHttpMock()`
|
||||
// method above creates (which Trusted Apps HTTP mocks also use)
|
||||
// FIXME: remove hard-coded IDs below and get them from the new FleetPackagePolicyGenerator (#2262)
|
||||
'ddf6570b-9175-4a6d-b288-61a09771c647',
|
||||
'b8e616ae-44fc-4be7-846c-ce8fa5c082dd',
|
||||
|
||||
// And finally, include any kql filters for package policies ids
|
||||
...getPackagePoliciesFromKueryString((query as { kuery?: string }).kuery ?? ''),
|
||||
];
|
||||
|
||||
return {
|
||||
items: [agentPolicy],
|
||||
perPage: 10,
|
||||
total: 1,
|
||||
items: requiredPolicyIds.map((packagePolicyId) => {
|
||||
return agentPolicyGenerator.generate({
|
||||
package_policies: [packagePolicyId],
|
||||
});
|
||||
}),
|
||||
perPage: Math.max(requiredPolicyIds.length, 10),
|
||||
total: requiredPolicyIds.length,
|
||||
page: 1,
|
||||
};
|
||||
},
|
||||
|
|
|
@ -7,48 +7,69 @@
|
|||
|
||||
import { HttpFetchOptionsWithPath } from 'kibana/public';
|
||||
import {
|
||||
ENDPOINT_TRUSTED_APPS_LIST_ID,
|
||||
EXCEPTION_LIST_ITEM_URL,
|
||||
EXCEPTION_LIST_URL,
|
||||
} from '@kbn/securitysolution-list-constants';
|
||||
import {
|
||||
ExceptionListItemSchema,
|
||||
FoundExceptionListItemSchema,
|
||||
FindExceptionListItemSchema,
|
||||
UpdateExceptionListItemSchema,
|
||||
ReadExceptionListItemSchema,
|
||||
CreateExceptionListItemSchema,
|
||||
ExceptionListSchema,
|
||||
} from '@kbn/securitysolution-io-ts-list-types';
|
||||
import {
|
||||
composeHttpHandlerMocks,
|
||||
httpHandlerMockFactory,
|
||||
ResponseProvidersInterface,
|
||||
} from '../../../common/mock/endpoint/http_handler_mock_factory';
|
||||
import { ExceptionsListItemGenerator } from '../../../../common/endpoint/data_generators/exceptions_list_item_generator';
|
||||
import { POLICY_REFERENCE_PREFIX } from '../../../../common/endpoint/service/trusted_apps/mapping';
|
||||
import { getTrustedAppsListSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_schema.mock';
|
||||
import {
|
||||
GetTrustedAppsListRequest,
|
||||
GetTrustedAppsListResponse,
|
||||
PutTrustedAppUpdateRequest,
|
||||
PutTrustedAppUpdateResponse,
|
||||
} from '../../../../common/endpoint/types';
|
||||
import {
|
||||
TRUSTED_APPS_LIST_API,
|
||||
TRUSTED_APPS_UPDATE_API,
|
||||
} from '../../../../common/endpoint/constants';
|
||||
import { TrustedAppGenerator } from '../../../../common/endpoint/data_generators/trusted_app_generator';
|
||||
fleetGetAgentPolicyListHttpMock,
|
||||
FleetGetAgentPolicyListHttpMockInterface,
|
||||
fleetGetEndpointPackagePolicyListHttpMock,
|
||||
FleetGetEndpointPackagePolicyListHttpMockInterface,
|
||||
} from './fleet_mocks';
|
||||
|
||||
export type PolicyDetailsGetTrustedAppsListHttpMocksInterface = ResponseProvidersInterface<{
|
||||
trustedAppsList: (options: HttpFetchOptionsWithPath) => GetTrustedAppsListResponse;
|
||||
interface FindExceptionListItemSchemaQueryParams
|
||||
extends Omit<FindExceptionListItemSchema, 'page' | 'per_page'> {
|
||||
page?: number;
|
||||
per_page?: number;
|
||||
}
|
||||
|
||||
export type TrustedAppsGetListHttpMocksInterface = ResponseProvidersInterface<{
|
||||
trustedAppsList: (options: HttpFetchOptionsWithPath) => FoundExceptionListItemSchema;
|
||||
}>;
|
||||
/**
|
||||
* HTTP mock for retrieving list of Trusted Apps
|
||||
*/
|
||||
export const trustedAppsGetListHttpMocks =
|
||||
httpHandlerMockFactory<PolicyDetailsGetTrustedAppsListHttpMocksInterface>([
|
||||
httpHandlerMockFactory<TrustedAppsGetListHttpMocksInterface>([
|
||||
{
|
||||
id: 'trustedAppsList',
|
||||
path: TRUSTED_APPS_LIST_API,
|
||||
path: `${EXCEPTION_LIST_ITEM_URL}/_find`,
|
||||
method: 'get',
|
||||
handler: ({ query }): GetTrustedAppsListResponse => {
|
||||
const apiQueryParams = query as GetTrustedAppsListRequest;
|
||||
const generator = new TrustedAppGenerator('seed');
|
||||
handler: ({ query }): FoundExceptionListItemSchema => {
|
||||
const apiQueryParams = query as unknown as FindExceptionListItemSchemaQueryParams;
|
||||
const generator = new ExceptionsListItemGenerator('seed');
|
||||
const perPage = apiQueryParams.per_page ?? 10;
|
||||
const data = Array.from({ length: Math.min(perPage, 50) }, () => generator.generate());
|
||||
const data = Array.from({ length: Math.min(perPage, 50) }, () =>
|
||||
generator.generate({ list_id: ENDPOINT_TRUSTED_APPS_LIST_ID })
|
||||
);
|
||||
|
||||
// FIXME: remove hard-coded IDs below adn get them from the new FleetPackagePolicyGenerator (#2262)
|
||||
|
||||
// Change the 3rd entry (index 2) to be policy specific
|
||||
data[2].effectScope = {
|
||||
type: 'policy',
|
||||
policies: [
|
||||
// IDs below are those generated by the `fleetGetEndpointPackagePolicyListHttpMock()` mock
|
||||
'ddf6570b-9175-4a6d-b288-61a09771c647',
|
||||
'b8e616ae-44fc-4be7-846c-ce8fa5c082dd',
|
||||
],
|
||||
};
|
||||
data[2].tags = [
|
||||
// IDs below are those generated by the `fleetGetEndpointPackagePolicyListHttpMock()` mock,
|
||||
// so if using in combination with that API mock, these should just "work"
|
||||
`${POLICY_REFERENCE_PREFIX}ddf6570b-9175-4a6d-b288-61a09771c647`,
|
||||
`${POLICY_REFERENCE_PREFIX}b8e616ae-44fc-4be7-846c-ce8fa5c082dd`,
|
||||
];
|
||||
|
||||
return {
|
||||
page: apiQueryParams.page ?? 1,
|
||||
|
@ -61,7 +82,7 @@ export const trustedAppsGetListHttpMocks =
|
|||
]);
|
||||
|
||||
export type TrustedAppPutHttpMocksInterface = ResponseProvidersInterface<{
|
||||
trustedAppUpdate: (options: HttpFetchOptionsWithPath) => PutTrustedAppUpdateResponse;
|
||||
trustedAppUpdate: (options: HttpFetchOptionsWithPath) => ExceptionListItemSchema;
|
||||
}>;
|
||||
/**
|
||||
* HTTP mocks that support updating a single Trusted Apps
|
||||
|
@ -69,23 +90,113 @@ export type TrustedAppPutHttpMocksInterface = ResponseProvidersInterface<{
|
|||
export const trustedAppPutHttpMocks = httpHandlerMockFactory<TrustedAppPutHttpMocksInterface>([
|
||||
{
|
||||
id: 'trustedAppUpdate',
|
||||
path: TRUSTED_APPS_UPDATE_API,
|
||||
path: EXCEPTION_LIST_ITEM_URL,
|
||||
method: 'put',
|
||||
handler: ({ body, path }): PutTrustedAppUpdateResponse => {
|
||||
const response: PutTrustedAppUpdateResponse = {
|
||||
data: {
|
||||
...(body as unknown as PutTrustedAppUpdateRequest),
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
id: path.split('/').pop()!,
|
||||
created_at: '2021-10-12T16:02:55.856Z',
|
||||
created_by: 'elastic',
|
||||
updated_at: '2021-10-13T16:02:55.856Z',
|
||||
updated_by: 'elastic',
|
||||
version: 'abc',
|
||||
},
|
||||
handler: ({ body, path }): ExceptionListItemSchema => {
|
||||
const updatedExceptionItem = JSON.parse(
|
||||
body as string
|
||||
) as Required<UpdateExceptionListItemSchema>;
|
||||
const response: ExceptionListItemSchema = {
|
||||
...updatedExceptionItem,
|
||||
id: path.split('/').pop() ?? 'unknown-id',
|
||||
comments: [],
|
||||
created_at: '2021-10-12T16:02:55.856Z',
|
||||
created_by: 'elastic',
|
||||
updated_at: '2021-10-13T16:02:55.856Z',
|
||||
updated_by: 'elastic',
|
||||
list_id: ENDPOINT_TRUSTED_APPS_LIST_ID,
|
||||
_version: 'abc',
|
||||
tie_breaker_id: '1111',
|
||||
};
|
||||
|
||||
return response;
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
export type TrustedAppsGetOneHttpMocksInterface = ResponseProvidersInterface<{
|
||||
trustedApp: (options: HttpFetchOptionsWithPath) => ExceptionListItemSchema;
|
||||
}>;
|
||||
/**
|
||||
* HTTP mock for retrieving list of Trusted Apps
|
||||
*/
|
||||
export const trustedAppsGetOneHttpMocks =
|
||||
httpHandlerMockFactory<TrustedAppsGetOneHttpMocksInterface>([
|
||||
{
|
||||
id: 'trustedApp',
|
||||
path: EXCEPTION_LIST_ITEM_URL,
|
||||
method: 'get',
|
||||
handler: ({ query }): ExceptionListItemSchema => {
|
||||
const apiQueryParams = query as ReadExceptionListItemSchema;
|
||||
const exceptionItem = new ExceptionsListItemGenerator('seed').generate();
|
||||
|
||||
exceptionItem.item_id = apiQueryParams.item_id ?? exceptionItem.item_id;
|
||||
exceptionItem.namespace_type =
|
||||
apiQueryParams.namespace_type ?? exceptionItem.namespace_type;
|
||||
|
||||
return exceptionItem;
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
export type TrustedAppPostHttpMocksInterface = ResponseProvidersInterface<{
|
||||
trustedAppCreate: (options: HttpFetchOptionsWithPath) => ExceptionListItemSchema;
|
||||
}>;
|
||||
/**
|
||||
* HTTP mocks that support updating a single Trusted Apps
|
||||
*/
|
||||
export const trustedAppPostHttpMocks = httpHandlerMockFactory<TrustedAppPostHttpMocksInterface>([
|
||||
{
|
||||
id: 'trustedAppCreate',
|
||||
path: EXCEPTION_LIST_ITEM_URL,
|
||||
method: 'post',
|
||||
handler: ({ body, path }): ExceptionListItemSchema => {
|
||||
const { comments, ...updatedExceptionItem } = JSON.parse(
|
||||
body as string
|
||||
) as CreateExceptionListItemSchema;
|
||||
const response: ExceptionListItemSchema = {
|
||||
...new ExceptionsListItemGenerator('seed').generate(),
|
||||
...updatedExceptionItem,
|
||||
};
|
||||
response.id = path.split('/').pop() ?? response.id;
|
||||
|
||||
return response;
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
export type TrustedAppsPostCreateListHttpMockInterface = ResponseProvidersInterface<{
|
||||
trustedAppCreateList: (options: HttpFetchOptionsWithPath) => ExceptionListSchema;
|
||||
}>;
|
||||
/**
|
||||
* HTTP mocks that support updating a single Trusted Apps
|
||||
*/
|
||||
export const trustedAppsPostCreateListHttpMock =
|
||||
httpHandlerMockFactory<TrustedAppsPostCreateListHttpMockInterface>([
|
||||
{
|
||||
id: 'trustedAppCreateList',
|
||||
path: EXCEPTION_LIST_URL,
|
||||
method: 'post',
|
||||
handler: (): ExceptionListSchema => {
|
||||
return getTrustedAppsListSchemaMock();
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
export type TrustedAppsAllHttpMocksInterface = FleetGetEndpointPackagePolicyListHttpMockInterface &
|
||||
FleetGetAgentPolicyListHttpMockInterface &
|
||||
TrustedAppsGetListHttpMocksInterface &
|
||||
TrustedAppsGetOneHttpMocksInterface &
|
||||
TrustedAppPutHttpMocksInterface &
|
||||
TrustedAppPostHttpMocksInterface &
|
||||
TrustedAppsPostCreateListHttpMockInterface;
|
||||
/** Use this HTTP mock when wanting to mock the API calls done by the Trusted Apps Http service */
|
||||
export const trustedAppsAllHttpMocks = composeHttpHandlerMocks<TrustedAppsAllHttpMocksInterface>([
|
||||
trustedAppsGetListHttpMocks,
|
||||
trustedAppsGetOneHttpMocks,
|
||||
trustedAppPutHttpMocks,
|
||||
trustedAppPostHttpMocks,
|
||||
trustedAppsPostCreateListHttpMock,
|
||||
fleetGetEndpointPackagePolicyListHttpMock,
|
||||
fleetGetAgentPolicyListHttpMock,
|
||||
]);
|
||||
|
|
|
@ -14,15 +14,16 @@ import { TrustedAppsHttpService } from '../../../../trusted_apps/service';
|
|||
export const policyDetailsMiddlewareFactory: ImmutableMiddlewareFactory<PolicyDetailsState> = (
|
||||
coreStart
|
||||
) => {
|
||||
// Initialize services needed by Policy middleware
|
||||
const trustedAppsService = new TrustedAppsHttpService(coreStart.http);
|
||||
const middlewareContext: MiddlewareRunnerContext = {
|
||||
coreStart,
|
||||
trustedAppsService,
|
||||
};
|
||||
|
||||
return (store) => (next) => async (action) => {
|
||||
next(action);
|
||||
|
||||
const trustedAppsService = new TrustedAppsHttpService(coreStart.http);
|
||||
const middlewareContext: MiddlewareRunnerContext = {
|
||||
coreStart,
|
||||
trustedAppsService,
|
||||
};
|
||||
|
||||
policySettingsMiddlewareRunner(middlewareContext, store, action);
|
||||
policyTrustedAppsMiddlewareRunner(middlewareContext, store, action);
|
||||
};
|
||||
|
|
|
@ -23,9 +23,15 @@ import {
|
|||
fleetGetEndpointPackagePolicyListHttpMock,
|
||||
FleetGetEndpointPackagePolicyListHttpMockInterface,
|
||||
trustedAppsGetListHttpMocks,
|
||||
PolicyDetailsGetTrustedAppsListHttpMocksInterface,
|
||||
TrustedAppsGetListHttpMocksInterface,
|
||||
trustedAppPutHttpMocks,
|
||||
TrustedAppPutHttpMocksInterface,
|
||||
trustedAppsGetOneHttpMocks,
|
||||
TrustedAppsGetOneHttpMocksInterface,
|
||||
fleetGetAgentPolicyListHttpMock,
|
||||
FleetGetAgentPolicyListHttpMockInterface,
|
||||
trustedAppsPostCreateListHttpMock,
|
||||
TrustedAppsPostCreateListHttpMockInterface,
|
||||
} from '../../mocks';
|
||||
|
||||
export const getMockListResponse: () => GetTrustedAppsListResponse = () => ({
|
||||
|
@ -71,13 +77,19 @@ export type PolicyDetailsPageAllApiHttpMocksInterface =
|
|||
FleetGetEndpointPackagePolicyHttpMockInterface &
|
||||
FleetGetAgentStatusHttpMockInterface &
|
||||
FleetGetEndpointPackagePolicyListHttpMockInterface &
|
||||
PolicyDetailsGetTrustedAppsListHttpMocksInterface &
|
||||
TrustedAppPutHttpMocksInterface;
|
||||
FleetGetAgentPolicyListHttpMockInterface &
|
||||
TrustedAppsGetListHttpMocksInterface &
|
||||
TrustedAppPutHttpMocksInterface &
|
||||
TrustedAppsGetOneHttpMocksInterface &
|
||||
TrustedAppsPostCreateListHttpMockInterface;
|
||||
export const policyDetailsPageAllApiHttpMocks =
|
||||
composeHttpHandlerMocks<PolicyDetailsPageAllApiHttpMocksInterface>([
|
||||
fleetGetEndpointPackagePolicyHttpMock,
|
||||
fleetGetAgentStatusHttpMock,
|
||||
fleetGetEndpointPackagePolicyListHttpMock,
|
||||
fleetGetAgentPolicyListHttpMock,
|
||||
trustedAppsGetListHttpMocks,
|
||||
trustedAppPutHttpMocks,
|
||||
trustedAppsGetOneHttpMocks,
|
||||
trustedAppsPostCreateListHttpMock,
|
||||
]);
|
||||
|
|
|
@ -14,39 +14,26 @@ import {
|
|||
} from '../../../../../../common/mock/endpoint';
|
||||
import { MiddlewareActionSpyHelper } from '../../../../../../common/store/test_utils';
|
||||
|
||||
import { TrustedAppsHttpService } from '../../../../trusted_apps/service';
|
||||
import { PolicyDetailsState } from '../../../types';
|
||||
import { getMockCreateResponse, getMockListResponse } from '../../../test_utils';
|
||||
import { createLoadedResourceState, isLoadedResourceState } from '../../../../../state';
|
||||
import { getPolicyDetailsArtifactsListPath } from '../../../../../common/routing';
|
||||
import { trustedAppsAllHttpMocks } from '../../../../mocks';
|
||||
import { HttpFetchOptionsWithPath } from 'kibana/public';
|
||||
|
||||
jest.mock('../../../../trusted_apps/service');
|
||||
jest.mock('../../../../../../common/components/user_privileges/endpoint/use_endpoint_privileges');
|
||||
|
||||
let mockedContext: AppContextTestRender;
|
||||
let waitForAction: MiddlewareActionSpyHelper['waitForAction'];
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let mockedApis: ReturnType<typeof trustedAppsAllHttpMocks>;
|
||||
const act = reactTestingLibrary.act;
|
||||
const TrustedAppsHttpServiceMock = TrustedAppsHttpService as jest.Mock;
|
||||
let getState: () => PolicyDetailsState;
|
||||
|
||||
describe('Policy trusted apps flyout', () => {
|
||||
beforeEach(() => {
|
||||
TrustedAppsHttpServiceMock.mockImplementation(() => {
|
||||
return {
|
||||
getTrustedAppsList: () => getMockListResponse(),
|
||||
updateTrustedApp: () => ({
|
||||
data: getMockCreateResponse(),
|
||||
}),
|
||||
assignPolicyToTrustedApps: () => [
|
||||
{
|
||||
data: getMockCreateResponse(),
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
mockedContext = createAppRootMockRenderer();
|
||||
waitForAction = mockedContext.middlewareSpy.waitForAction;
|
||||
mockedApis = trustedAppsAllHttpMocks(mockedContext.coreStart.http);
|
||||
getState = () => mockedContext.store.getState().management.policyDetails;
|
||||
render = () => mockedContext.render(<PolicyTrustedAppsFlyout />);
|
||||
});
|
||||
|
@ -58,11 +45,13 @@ describe('Policy trusted apps flyout', () => {
|
|||
validate: (action) => isLoadedResourceState(action.payload),
|
||||
});
|
||||
|
||||
TrustedAppsHttpServiceMock.mockImplementation(() => {
|
||||
return {
|
||||
getTrustedAppsList: () => ({ data: [] }),
|
||||
};
|
||||
mockedApis.responseProvider.trustedAppsList.mockReturnValue({
|
||||
data: [],
|
||||
total: 0,
|
||||
per_page: 10,
|
||||
page: 1,
|
||||
});
|
||||
|
||||
const component = render();
|
||||
|
||||
mockedContext.history.push(getPolicyDetailsArtifactsListPath('1234', { show: 'list' }));
|
||||
|
@ -77,10 +66,11 @@ describe('Policy trusted apps flyout', () => {
|
|||
});
|
||||
|
||||
it('should renders flyout open correctly without data', async () => {
|
||||
TrustedAppsHttpServiceMock.mockImplementation(() => {
|
||||
return {
|
||||
getTrustedAppsList: () => ({ data: [] }),
|
||||
};
|
||||
mockedApis.responseProvider.trustedAppsList.mockReturnValue({
|
||||
data: [],
|
||||
total: 0,
|
||||
per_page: 10,
|
||||
page: 1,
|
||||
});
|
||||
const component = render();
|
||||
|
||||
|
@ -107,36 +97,37 @@ describe('Policy trusted apps flyout', () => {
|
|||
});
|
||||
|
||||
expect(component.getByTestId('confirmPolicyTrustedAppsFlyout')).not.toBeNull();
|
||||
expect(component.getByTestId(`${getMockListResponse().data[0].name}_checkbox`)).not.toBeNull();
|
||||
expect(component.getByTestId('Generated Exception (u6kh2)_checkbox')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should confirm flyout action', async () => {
|
||||
const waitForUpdate = waitForAction('policyArtifactsUpdateTrustedAppsChanged', {
|
||||
validate: (action) => isLoadedResourceState(action.payload),
|
||||
});
|
||||
const waitChangeUrl = waitForAction('userChangedUrl');
|
||||
const component = render();
|
||||
|
||||
mockedContext.history.push(getPolicyDetailsArtifactsListPath('1234', { show: 'list' }));
|
||||
mockedContext.history.push(
|
||||
getPolicyDetailsArtifactsListPath('2d95bec3-b48f-4db7-9622-a2b061cc031d', { show: 'list' })
|
||||
);
|
||||
await waitForAction('policyArtifactsAssignableListPageDataChanged', {
|
||||
validate: (action) => isLoadedResourceState(action.payload),
|
||||
});
|
||||
|
||||
const tACardCheckbox = component.getByTestId(`${getMockListResponse().data[0].name}_checkbox`);
|
||||
// TA name below in the selector matches the 3rd generated trusted app which is policy specific
|
||||
const tACardCheckbox = component.getByTestId('Generated Exception (3xnng)_checkbox');
|
||||
|
||||
await act(async () => {
|
||||
act(() => {
|
||||
fireEvent.click(tACardCheckbox);
|
||||
});
|
||||
|
||||
const waitChangeUrl = waitForAction('userChangedUrl');
|
||||
const confirmButton = component.getByTestId('confirmPolicyTrustedAppsFlyout');
|
||||
|
||||
await act(async () => {
|
||||
act(() => {
|
||||
fireEvent.click(confirmButton);
|
||||
});
|
||||
|
||||
await waitForUpdate;
|
||||
await waitChangeUrl;
|
||||
|
||||
const currentLocation = getState().artifacts.location;
|
||||
|
||||
expect(currentLocation.show).toBeUndefined();
|
||||
});
|
||||
|
||||
|
@ -161,11 +152,13 @@ describe('Policy trusted apps flyout', () => {
|
|||
});
|
||||
|
||||
it('should display warning message when too much results', async () => {
|
||||
TrustedAppsHttpServiceMock.mockImplementation(() => {
|
||||
return {
|
||||
getTrustedAppsList: () => ({ ...getMockListResponse(), total: 101 }),
|
||||
};
|
||||
});
|
||||
const listResponse = {
|
||||
...mockedApis.responseProvider.trustedAppsList.getMockImplementation()!({
|
||||
query: {},
|
||||
} as HttpFetchOptionsWithPath),
|
||||
total: 101,
|
||||
};
|
||||
mockedApis.responseProvider.trustedAppsList.mockReturnValue(listResponse);
|
||||
|
||||
const component = render();
|
||||
|
||||
|
|
|
@ -13,32 +13,32 @@ import {
|
|||
} from '../../../../../../common/mock/endpoint';
|
||||
import { MiddlewareActionSpyHelper } from '../../../../../../common/store/test_utils';
|
||||
|
||||
import { TrustedAppsHttpService } from '../../../../trusted_apps/service';
|
||||
import { getMockListResponse } from '../../../test_utils';
|
||||
import { createLoadedResourceState, isLoadedResourceState } from '../../../../../state';
|
||||
import { getPolicyDetailsArtifactsListPath } from '../../../../../common/routing';
|
||||
import { EndpointDocGenerator } from '../../../../../../../common/endpoint/generate_data';
|
||||
import { policyListApiPathHandlers } from '../../../store/test_mock_utils';
|
||||
import { useEndpointPrivileges } from '../../../../../../common/components/user_privileges/endpoint/use_endpoint_privileges';
|
||||
import { getEndpointPrivilegesInitialStateMock } from '../../../../../../common/components/user_privileges/endpoint/mocks';
|
||||
import { trustedAppsAllHttpMocks } from '../../../../mocks';
|
||||
|
||||
jest.mock('../../../../trusted_apps/service');
|
||||
jest.mock('../../../../../../common/components/user_privileges/endpoint/use_endpoint_privileges');
|
||||
const mockUseEndpointPrivileges = useEndpointPrivileges as jest.Mock;
|
||||
|
||||
let mockedContext: AppContextTestRender;
|
||||
let waitForAction: MiddlewareActionSpyHelper['waitForAction'];
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
const TrustedAppsHttpServiceMock = TrustedAppsHttpService as jest.Mock;
|
||||
let coreStart: AppContextTestRender['coreStart'];
|
||||
let http: typeof coreStart.http;
|
||||
let mockedApis: ReturnType<typeof trustedAppsAllHttpMocks>;
|
||||
const generator = new EndpointDocGenerator();
|
||||
|
||||
describe('Policy trusted apps layout', () => {
|
||||
beforeEach(() => {
|
||||
mockedContext = createAppRootMockRenderer();
|
||||
http = mockedContext.coreStart.http;
|
||||
|
||||
const policyListApiHandlers = policyListApiPathHandlers();
|
||||
|
||||
http.get.mockImplementation((...args) => {
|
||||
const [path] = args;
|
||||
if (typeof path === 'string') {
|
||||
|
@ -67,12 +67,8 @@ describe('Policy trusted apps layout', () => {
|
|||
|
||||
return Promise.reject(new Error(`unknown API call (not MOCKED): ${path}`));
|
||||
});
|
||||
TrustedAppsHttpServiceMock.mockImplementation(() => {
|
||||
return {
|
||||
getTrustedAppsList: () => ({ data: [] }),
|
||||
};
|
||||
});
|
||||
|
||||
mockedApis = trustedAppsAllHttpMocks(http);
|
||||
waitForAction = mockedContext.middlewareSpy.waitForAction;
|
||||
render = () => mockedContext.render(<PolicyTrustedAppsLayout />);
|
||||
});
|
||||
|
@ -84,9 +80,14 @@ describe('Policy trusted apps layout', () => {
|
|||
afterEach(() => reactTestingLibrary.cleanup());
|
||||
|
||||
it('should renders layout with no existing TA data', async () => {
|
||||
const component = render();
|
||||
|
||||
mockedApis.responseProvider.trustedAppsList.mockImplementation(() => ({
|
||||
data: [],
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
total: 0,
|
||||
}));
|
||||
mockedContext.history.push(getPolicyDetailsArtifactsListPath('1234'));
|
||||
const component = render();
|
||||
|
||||
await waitForAction('policyArtifactsDeosAnyTrustedAppExists', {
|
||||
validate: (action) => isLoadedResourceState(action.payload),
|
||||
|
@ -96,8 +97,14 @@ describe('Policy trusted apps layout', () => {
|
|||
});
|
||||
|
||||
it('should renders layout with no assigned TA data', async () => {
|
||||
const component = render();
|
||||
mockedApis.responseProvider.trustedAppsList.mockImplementation(() => ({
|
||||
data: [],
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
total: 0,
|
||||
}));
|
||||
mockedContext.history.push(getPolicyDetailsArtifactsListPath('1234'));
|
||||
const component = render();
|
||||
|
||||
await waitForAction('assignedTrustedAppsListStateChanged');
|
||||
|
||||
|
@ -110,13 +117,8 @@ describe('Policy trusted apps layout', () => {
|
|||
});
|
||||
|
||||
it('should renders layout with data', async () => {
|
||||
TrustedAppsHttpServiceMock.mockImplementation(() => {
|
||||
return {
|
||||
getTrustedAppsList: () => getMockListResponse(),
|
||||
};
|
||||
});
|
||||
const component = render();
|
||||
mockedContext.history.push(getPolicyDetailsArtifactsListPath('1234'));
|
||||
const component = render();
|
||||
|
||||
await waitForAction('assignedTrustedAppsListStateChanged');
|
||||
|
||||
|
@ -147,11 +149,6 @@ describe('Policy trusted apps layout', () => {
|
|||
isPlatinumPlus: false,
|
||||
})
|
||||
);
|
||||
TrustedAppsHttpServiceMock.mockImplementation(() => {
|
||||
return {
|
||||
getTrustedAppsList: () => getMockListResponse(),
|
||||
};
|
||||
});
|
||||
const component = render();
|
||||
mockedContext.history.push(getPolicyDetailsArtifactsListPath('1234'));
|
||||
|
||||
|
|
|
@ -202,7 +202,7 @@ describe('when rendering the PolicyTrustedAppsList', () => {
|
|||
expect(appTestContext.coreStart.application.navigateToApp).toHaveBeenCalledWith(
|
||||
APP_UI_ID,
|
||||
expect.objectContaining({
|
||||
path: '/administration/trusted_apps?filter=89f72d8a-05b5-4350-8cad-0dc3661d6e67',
|
||||
path: '/administration/trusted_apps?filter=6f12b025-fcb0-4db4-99e5-4927e3502bb8',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
@ -324,12 +324,12 @@ describe('when rendering the PolicyTrustedAppsList', () => {
|
|||
});
|
||||
|
||||
it('does not show remove option in actions menu if license is downgraded to gold or below', async () => {
|
||||
await render();
|
||||
mockUseEndpointPrivileges.mockReturnValue(
|
||||
loadedUserEndpointPrivilegesState({
|
||||
isPlatinumPlus: false,
|
||||
})
|
||||
);
|
||||
await render();
|
||||
await toggleCardActionMenu(POLICY_SPECIFIC_CARD_INDEX);
|
||||
|
||||
expect(renderResult.queryByTestId('policyTrustedAppsGrid-removeAction')).toBeNull();
|
||||
|
|
|
@ -24,6 +24,7 @@ import {
|
|||
} from '../../../store/policy_details/action/policy_trusted_apps_action';
|
||||
import { Immutable } from '../../../../../../../common/endpoint/types';
|
||||
import { HttpFetchOptionsWithPath } from 'kibana/public';
|
||||
import { exceptionListItemToTrustedApp } from '../../../../trusted_apps/service/mappers';
|
||||
|
||||
describe('When using the RemoveTrustedAppFromPolicyModal component', () => {
|
||||
let appTestContext: AppContextTestRender;
|
||||
|
@ -41,8 +42,10 @@ describe('When using the RemoveTrustedAppFromPolicyModal component', () => {
|
|||
mockedApis = policyDetailsPageAllApiHttpMocks(appTestContext.coreStart.http);
|
||||
trustedApps = [
|
||||
// The 3rd trusted app generated by the HTTP mock is a Policy Specific one
|
||||
mockedApis.responseProvider.trustedAppsList({ query: {} } as HttpFetchOptionsWithPath)
|
||||
.data[2],
|
||||
exceptionListItemToTrustedApp(
|
||||
mockedApis.responseProvider.trustedAppsList({ query: {} } as HttpFetchOptionsWithPath)
|
||||
.data[2]
|
||||
),
|
||||
];
|
||||
|
||||
// Delay the Update Trusted App API response so that we can test UI states while the update is underway.
|
||||
|
@ -211,7 +214,7 @@ describe('When using the RemoveTrustedAppFromPolicyModal component', () => {
|
|||
await clickConfirmButton(true, true);
|
||||
|
||||
expect(appTestContext.coreStart.notifications.toasts.addSuccess).toHaveBeenCalledWith({
|
||||
text: '"Avast Business Antivirus" has been removed from Endpoint Policy policy',
|
||||
text: '"Generated Exception (3xnng)" has been removed from Endpoint Policy policy',
|
||||
title: 'Successfully removed',
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,6 +5,16 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
CreateExceptionListSchema,
|
||||
ExceptionListTypeEnum,
|
||||
} from '@kbn/securitysolution-io-ts-list-types';
|
||||
import {
|
||||
ENDPOINT_TRUSTED_APPS_LIST_DESCRIPTION,
|
||||
ENDPOINT_TRUSTED_APPS_LIST_ID,
|
||||
ENDPOINT_TRUSTED_APPS_LIST_NAME,
|
||||
} from '@kbn/securitysolution-list-constants';
|
||||
|
||||
export const SEARCHABLE_FIELDS: Readonly<string[]> = [
|
||||
`name`,
|
||||
`description`,
|
||||
|
@ -12,3 +22,11 @@ export const SEARCHABLE_FIELDS: Readonly<string[]> = [
|
|||
`entries.value`,
|
||||
`entries.entries.value`,
|
||||
];
|
||||
|
||||
export const TRUSTED_APPS_EXCEPTION_LIST_DEFINITION: CreateExceptionListSchema = {
|
||||
name: ENDPOINT_TRUSTED_APPS_LIST_NAME,
|
||||
namespace_type: 'agnostic',
|
||||
description: ENDPOINT_TRUSTED_APPS_LIST_DESCRIPTION,
|
||||
list_id: ENDPOINT_TRUSTED_APPS_LIST_ID,
|
||||
type: ExceptionListTypeEnum.ENDPOINT_TRUSTED_APPS,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 { EndpointError } from '../../../../../common/endpoint/errors';
|
||||
|
||||
export class HttpRequestValidationError extends EndpointError<string[]> {
|
||||
public readonly body: { message: string };
|
||||
constructor(validationFailures: string[]) {
|
||||
super('Invalid trusted application', validationFailures);
|
||||
// Attempts to mirror an HTTP API error body
|
||||
this.body = {
|
||||
message: validationFailures.join(', ') ?? 'unknown',
|
||||
};
|
||||
}
|
||||
}
|
|
@ -5,186 +5,4 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { HttpStart } from 'kibana/public';
|
||||
|
||||
import pMap from 'p-map';
|
||||
import {
|
||||
TRUSTED_APPS_CREATE_API,
|
||||
TRUSTED_APPS_DELETE_API,
|
||||
TRUSTED_APPS_GET_API,
|
||||
TRUSTED_APPS_LIST_API,
|
||||
TRUSTED_APPS_UPDATE_API,
|
||||
TRUSTED_APPS_SUMMARY_API,
|
||||
} from '../../../../../common/endpoint/constants';
|
||||
|
||||
import {
|
||||
DeleteTrustedAppsRequestParams,
|
||||
GetTrustedAppsListResponse,
|
||||
GetTrustedAppsListRequest,
|
||||
PostTrustedAppCreateRequest,
|
||||
PostTrustedAppCreateResponse,
|
||||
GetTrustedAppsSummaryResponse,
|
||||
PutTrustedAppUpdateRequest,
|
||||
PutTrustedAppUpdateResponse,
|
||||
PutTrustedAppsRequestParams,
|
||||
GetOneTrustedAppRequestParams,
|
||||
GetOneTrustedAppResponse,
|
||||
GetTrustedAppsSummaryRequest,
|
||||
TrustedApp,
|
||||
MaybeImmutable,
|
||||
} from '../../../../../common/endpoint/types';
|
||||
import { resolvePathVariables } from '../../../../common/utils/resolve_path_variables';
|
||||
|
||||
import { sendGetEndpointSpecificPackagePolicies } from '../../policy/store/services/ingest';
|
||||
import { toUpdateTrustedApp } from '../../../../../common/endpoint/service/trusted_apps/to_update_trusted_app';
|
||||
import { isGlobalEffectScope } from '../state/type_guards';
|
||||
|
||||
export interface TrustedAppsService {
|
||||
getTrustedApp(params: GetOneTrustedAppRequestParams): Promise<GetOneTrustedAppResponse>;
|
||||
getTrustedAppsList(request: GetTrustedAppsListRequest): Promise<GetTrustedAppsListResponse>;
|
||||
deleteTrustedApp(request: DeleteTrustedAppsRequestParams): Promise<void>;
|
||||
createTrustedApp(request: PostTrustedAppCreateRequest): Promise<PostTrustedAppCreateResponse>;
|
||||
updateTrustedApp(
|
||||
params: PutTrustedAppsRequestParams,
|
||||
request: PutTrustedAppUpdateRequest
|
||||
): Promise<PutTrustedAppUpdateResponse>;
|
||||
getPolicyList(
|
||||
options?: Parameters<typeof sendGetEndpointSpecificPackagePolicies>[1]
|
||||
): ReturnType<typeof sendGetEndpointSpecificPackagePolicies>;
|
||||
assignPolicyToTrustedApps(
|
||||
policyId: string,
|
||||
trustedApps: MaybeImmutable<TrustedApp[]>
|
||||
): Promise<PutTrustedAppUpdateResponse[]>;
|
||||
removePolicyFromTrustedApps(
|
||||
policyId: string,
|
||||
trustedApps: MaybeImmutable<TrustedApp[]>
|
||||
): Promise<PutTrustedAppUpdateResponse[]>;
|
||||
}
|
||||
|
||||
const P_MAP_OPTIONS = Object.freeze<pMap.Options>({
|
||||
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 {
|
||||
constructor(private http: HttpStart) {}
|
||||
|
||||
async getTrustedApp(params: GetOneTrustedAppRequestParams) {
|
||||
return this.http.get<GetOneTrustedAppResponse>(
|
||||
resolvePathVariables(TRUSTED_APPS_GET_API, params)
|
||||
);
|
||||
}
|
||||
|
||||
async getTrustedAppsList(request: GetTrustedAppsListRequest) {
|
||||
return this.http.get<GetTrustedAppsListResponse>(TRUSTED_APPS_LIST_API, {
|
||||
query: request,
|
||||
});
|
||||
}
|
||||
|
||||
async deleteTrustedApp(request: DeleteTrustedAppsRequestParams): Promise<void> {
|
||||
return this.http.delete<void>(resolvePathVariables(TRUSTED_APPS_DELETE_API, request));
|
||||
}
|
||||
|
||||
async createTrustedApp(request: PostTrustedAppCreateRequest) {
|
||||
return this.http.post<PostTrustedAppCreateResponse>(TRUSTED_APPS_CREATE_API, {
|
||||
body: JSON.stringify(request),
|
||||
});
|
||||
}
|
||||
|
||||
async updateTrustedApp(
|
||||
params: PutTrustedAppsRequestParams,
|
||||
updatedTrustedApp: PutTrustedAppUpdateRequest
|
||||
) {
|
||||
return this.http.put<PutTrustedAppUpdateResponse>(
|
||||
resolvePathVariables(TRUSTED_APPS_UPDATE_API, params),
|
||||
{ body: JSON.stringify(updatedTrustedApp) }
|
||||
);
|
||||
}
|
||||
|
||||
async getTrustedAppsSummary(request: GetTrustedAppsSummaryRequest) {
|
||||
return this.http.get<GetTrustedAppsSummaryResponse>(TRUSTED_APPS_SUMMARY_API, {
|
||||
query: request,
|
||||
});
|
||||
}
|
||||
|
||||
getPolicyList(options?: Parameters<typeof sendGetEndpointSpecificPackagePolicies>[1]) {
|
||||
return sendGetEndpointSpecificPackagePolicies(this.http, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a policy to trusted apps. Note that Trusted Apps MUST NOT be global
|
||||
*
|
||||
* @param policyId
|
||||
* @param trustedApps[]
|
||||
*/
|
||||
assignPolicyToTrustedApps(
|
||||
policyId: string,
|
||||
trustedApps: MaybeImmutable<TrustedApp[]>
|
||||
): Promise<PutTrustedAppUpdateResponse[]> {
|
||||
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<TrustedApp[]>
|
||||
): Promise<PutTrustedAppUpdateResponse[]> {
|
||||
return this._handleAssignOrRemovePolicyId('remove', policyId, trustedApps);
|
||||
}
|
||||
|
||||
private _handleAssignOrRemovePolicyId(
|
||||
action: 'assign' | 'remove',
|
||||
policyId: string,
|
||||
trustedApps: MaybeImmutable<TrustedApp[]>
|
||||
): Promise<PutTrustedAppUpdateResponse[]> {
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
export * from './trusted_apps_http_service';
|
||||
|
|
|
@ -0,0 +1,266 @@
|
|||
/*
|
||||
* 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,
|
||||
EntriesArray,
|
||||
EntryMatch,
|
||||
EntryMatchWildcard,
|
||||
EntryNested,
|
||||
ExceptionListItemSchema,
|
||||
NestedEntriesArray,
|
||||
OsType,
|
||||
UpdateExceptionListItemSchema,
|
||||
} from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '@kbn/securitysolution-list-constants';
|
||||
import {
|
||||
ConditionEntry,
|
||||
ConditionEntryField,
|
||||
EffectScope,
|
||||
NewTrustedApp,
|
||||
OperatingSystem,
|
||||
TrustedApp,
|
||||
TrustedAppEntryTypes,
|
||||
UpdateTrustedApp,
|
||||
} from '../../../../../common/endpoint/types';
|
||||
import {
|
||||
POLICY_REFERENCE_PREFIX,
|
||||
tagsToEffectScope,
|
||||
} from '../../../../../common/endpoint/service/trusted_apps/mapping';
|
||||
|
||||
type ConditionEntriesMap = { [K in ConditionEntryField]?: ConditionEntry<K> };
|
||||
type Mapping<T extends string, U> = { [K in T]: U };
|
||||
|
||||
const OS_TYPE_TO_OPERATING_SYSTEM: Mapping<OsType, OperatingSystem> = {
|
||||
linux: OperatingSystem.LINUX,
|
||||
macos: OperatingSystem.MAC,
|
||||
windows: OperatingSystem.WINDOWS,
|
||||
};
|
||||
|
||||
const OPERATING_SYSTEM_TO_OS_TYPE: Mapping<OperatingSystem, OsType> = {
|
||||
[OperatingSystem.LINUX]: 'linux',
|
||||
[OperatingSystem.MAC]: 'macos',
|
||||
[OperatingSystem.WINDOWS]: 'windows',
|
||||
};
|
||||
|
||||
const OPERATOR_VALUE = 'included';
|
||||
|
||||
const filterUndefined = <T>(list: Array<T | undefined>): T[] => {
|
||||
return list.filter((item: T | undefined): item is T => item !== undefined);
|
||||
};
|
||||
|
||||
const createConditionEntry = <T extends ConditionEntryField>(
|
||||
field: T,
|
||||
type: TrustedAppEntryTypes,
|
||||
value: string
|
||||
): ConditionEntry<T> => {
|
||||
return { field, value, type, operator: OPERATOR_VALUE };
|
||||
};
|
||||
|
||||
const entriesToConditionEntriesMap = (entries: EntriesArray): ConditionEntriesMap => {
|
||||
return entries.reduce((result, entry) => {
|
||||
if (entry.field.startsWith('process.hash') && entry.type === 'match') {
|
||||
return {
|
||||
...result,
|
||||
[ConditionEntryField.HASH]: createConditionEntry(
|
||||
ConditionEntryField.HASH,
|
||||
entry.type,
|
||||
entry.value
|
||||
),
|
||||
};
|
||||
} else if (
|
||||
entry.field === 'process.executable.caseless' &&
|
||||
(entry.type === 'match' || entry.type === 'wildcard')
|
||||
) {
|
||||
return {
|
||||
...result,
|
||||
[ConditionEntryField.PATH]: createConditionEntry(
|
||||
ConditionEntryField.PATH,
|
||||
entry.type,
|
||||
entry.value
|
||||
),
|
||||
};
|
||||
} else if (entry.field === 'process.Ext.code_signature' && entry.type === 'nested') {
|
||||
const subjectNameCondition = entry.entries.find((subEntry): subEntry is EntryMatch => {
|
||||
return subEntry.field === 'subject_name' && subEntry.type === 'match';
|
||||
});
|
||||
|
||||
if (subjectNameCondition) {
|
||||
return {
|
||||
...result,
|
||||
[ConditionEntryField.SIGNER]: createConditionEntry(
|
||||
ConditionEntryField.SIGNER,
|
||||
subjectNameCondition.type,
|
||||
subjectNameCondition.value
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}, {} as ConditionEntriesMap);
|
||||
};
|
||||
|
||||
/**
|
||||
* Map an ExceptionListItem to a TrustedApp item
|
||||
* @param exceptionListItem
|
||||
*/
|
||||
export const exceptionListItemToTrustedApp = (
|
||||
exceptionListItem: ExceptionListItemSchema
|
||||
): TrustedApp => {
|
||||
if (exceptionListItem.os_types[0]) {
|
||||
const os = osFromExceptionItem(exceptionListItem);
|
||||
const grouped = entriesToConditionEntriesMap(exceptionListItem.entries);
|
||||
|
||||
return {
|
||||
id: exceptionListItem.item_id,
|
||||
version: exceptionListItem._version || '',
|
||||
name: exceptionListItem.name,
|
||||
description: exceptionListItem.description,
|
||||
effectScope: tagsToEffectScope(exceptionListItem.tags),
|
||||
created_at: exceptionListItem.created_at,
|
||||
created_by: exceptionListItem.created_by,
|
||||
updated_at: exceptionListItem.updated_at,
|
||||
updated_by: exceptionListItem.updated_by,
|
||||
...(os === OperatingSystem.LINUX || os === OperatingSystem.MAC
|
||||
? {
|
||||
os,
|
||||
entries: filterUndefined([
|
||||
grouped[ConditionEntryField.HASH],
|
||||
grouped[ConditionEntryField.PATH],
|
||||
]),
|
||||
}
|
||||
: {
|
||||
os,
|
||||
entries: filterUndefined([
|
||||
grouped[ConditionEntryField.HASH],
|
||||
grouped[ConditionEntryField.PATH],
|
||||
grouped[ConditionEntryField.SIGNER],
|
||||
]),
|
||||
}),
|
||||
};
|
||||
} else {
|
||||
throw new Error('Unknown Operating System assigned to trusted application.');
|
||||
}
|
||||
};
|
||||
|
||||
const osFromExceptionItem = (exceptionListItem: ExceptionListItemSchema): TrustedApp['os'] => {
|
||||
return OS_TYPE_TO_OPERATING_SYSTEM[exceptionListItem.os_types[0]];
|
||||
};
|
||||
|
||||
const hashType = (hash: string): 'md5' | 'sha256' | 'sha1' | undefined => {
|
||||
switch (hash.length) {
|
||||
case 32:
|
||||
return 'md5';
|
||||
case 40:
|
||||
return 'sha1';
|
||||
case 64:
|
||||
return 'sha256';
|
||||
}
|
||||
};
|
||||
|
||||
const createEntryMatch = (field: string, value: string): EntryMatch => {
|
||||
return { field, value, type: 'match', operator: OPERATOR_VALUE };
|
||||
};
|
||||
|
||||
const createEntryMatchWildcard = (field: string, value: string): EntryMatchWildcard => {
|
||||
return { field, value, type: 'wildcard', operator: OPERATOR_VALUE };
|
||||
};
|
||||
|
||||
const createEntryNested = (field: string, entries: NestedEntriesArray): EntryNested => {
|
||||
return { field, entries, type: 'nested' };
|
||||
};
|
||||
|
||||
const effectScopeToTags = (effectScope: EffectScope) => {
|
||||
if (effectScope.type === 'policy') {
|
||||
return effectScope.policies.map((policy) => `${POLICY_REFERENCE_PREFIX}${policy}`);
|
||||
} else {
|
||||
return [`${POLICY_REFERENCE_PREFIX}all`];
|
||||
}
|
||||
};
|
||||
|
||||
const conditionEntriesToEntries = (conditionEntries: ConditionEntry[]): EntriesArray => {
|
||||
return conditionEntries.map((conditionEntry) => {
|
||||
if (conditionEntry.field === ConditionEntryField.HASH) {
|
||||
return createEntryMatch(
|
||||
`process.hash.${hashType(conditionEntry.value)}`,
|
||||
conditionEntry.value.toLowerCase()
|
||||
);
|
||||
} else if (conditionEntry.field === ConditionEntryField.SIGNER) {
|
||||
return createEntryNested(`process.Ext.code_signature`, [
|
||||
createEntryMatch('trusted', 'true'),
|
||||
createEntryMatch('subject_name', conditionEntry.value),
|
||||
]);
|
||||
} else if (
|
||||
conditionEntry.field === ConditionEntryField.PATH &&
|
||||
conditionEntry.type === 'wildcard'
|
||||
) {
|
||||
return createEntryMatchWildcard(`process.executable.caseless`, conditionEntry.value);
|
||||
} else {
|
||||
return createEntryMatch(`process.executable.caseless`, conditionEntry.value);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Map NewTrustedApp to CreateExceptionListItemOptions.
|
||||
*/
|
||||
export const newTrustedAppToCreateExceptionListItem = ({
|
||||
os,
|
||||
entries,
|
||||
name,
|
||||
description = '',
|
||||
effectScope,
|
||||
}: NewTrustedApp): CreateExceptionListItemSchema => {
|
||||
return {
|
||||
comments: [],
|
||||
description,
|
||||
entries: conditionEntriesToEntries(entries),
|
||||
list_id: ENDPOINT_TRUSTED_APPS_LIST_ID,
|
||||
meta: undefined,
|
||||
name,
|
||||
namespace_type: 'agnostic',
|
||||
os_types: [OPERATING_SYSTEM_TO_OS_TYPE[os]],
|
||||
tags: effectScopeToTags(effectScope),
|
||||
type: 'simple',
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Map UpdateTrustedApp to UpdateExceptionListItemOptions
|
||||
*
|
||||
* @param {ExceptionListItemSchema} currentTrustedAppExceptionItem
|
||||
* @param {UpdateTrustedApp} updatedTrustedApp
|
||||
*/
|
||||
export const updatedTrustedAppToUpdateExceptionListItem = (
|
||||
{
|
||||
id,
|
||||
item_id: itemId,
|
||||
namespace_type: namespaceType,
|
||||
type,
|
||||
comments,
|
||||
meta,
|
||||
}: ExceptionListItemSchema,
|
||||
{ os, entries, name, description = '', effectScope, version }: UpdateTrustedApp
|
||||
): UpdateExceptionListItemSchema => {
|
||||
return {
|
||||
_version: version,
|
||||
name,
|
||||
description,
|
||||
entries: conditionEntriesToEntries(entries),
|
||||
os_types: [OPERATING_SYSTEM_TO_OS_TYPE[os]],
|
||||
tags: effectScopeToTags(effectScope),
|
||||
|
||||
// Copied from current trusted app exception item
|
||||
id,
|
||||
comments,
|
||||
item_id: itemId,
|
||||
meta,
|
||||
namespace_type: namespaceType,
|
||||
type,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
* 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 pMap from 'p-map';
|
||||
import { HttpStart } from 'kibana/public';
|
||||
import {
|
||||
ENDPOINT_TRUSTED_APPS_LIST_ID,
|
||||
EXCEPTION_LIST_ITEM_URL,
|
||||
EXCEPTION_LIST_URL,
|
||||
} from '@kbn/securitysolution-list-constants';
|
||||
import {
|
||||
ExceptionListItemSchema,
|
||||
ExceptionListSchema,
|
||||
ExceptionListSummarySchema,
|
||||
FoundExceptionListItemSchema,
|
||||
} from '@kbn/securitysolution-io-ts-list-types';
|
||||
import {
|
||||
DeleteTrustedAppsRequestParams,
|
||||
GetOneTrustedAppRequestParams,
|
||||
GetOneTrustedAppResponse,
|
||||
GetTrustedAppsListRequest,
|
||||
GetTrustedAppsListResponse,
|
||||
GetTrustedAppsSummaryRequest,
|
||||
MaybeImmutable,
|
||||
PostTrustedAppCreateRequest,
|
||||
PostTrustedAppCreateResponse,
|
||||
PutTrustedAppsRequestParams,
|
||||
PutTrustedAppUpdateRequest,
|
||||
PutTrustedAppUpdateResponse,
|
||||
TrustedApp,
|
||||
} from '../../../../../common/endpoint/types';
|
||||
import { sendGetEndpointSpecificPackagePolicies } from '../../policy/store/services/ingest';
|
||||
import { isGlobalEffectScope } from '../state/type_guards';
|
||||
import { toUpdateTrustedApp } from '../../../../../common/endpoint/service/trusted_apps/to_update_trusted_app';
|
||||
import { validateTrustedAppHttpRequestBody } from './validate_trusted_app_http_request_body';
|
||||
import {
|
||||
exceptionListItemToTrustedApp,
|
||||
newTrustedAppToCreateExceptionListItem,
|
||||
updatedTrustedAppToUpdateExceptionListItem,
|
||||
} from './mappers';
|
||||
import { TRUSTED_APPS_EXCEPTION_LIST_DEFINITION } from '../constants';
|
||||
|
||||
export interface TrustedAppsService {
|
||||
getTrustedApp(params: GetOneTrustedAppRequestParams): Promise<GetOneTrustedAppResponse>;
|
||||
|
||||
getTrustedAppsList(request: GetTrustedAppsListRequest): Promise<GetTrustedAppsListResponse>;
|
||||
|
||||
deleteTrustedApp(request: DeleteTrustedAppsRequestParams): Promise<void>;
|
||||
|
||||
createTrustedApp(request: PostTrustedAppCreateRequest): Promise<PostTrustedAppCreateResponse>;
|
||||
|
||||
updateTrustedApp(
|
||||
params: PutTrustedAppsRequestParams,
|
||||
request: PutTrustedAppUpdateRequest
|
||||
): Promise<PutTrustedAppUpdateResponse>;
|
||||
|
||||
getPolicyList(
|
||||
options?: Parameters<typeof sendGetEndpointSpecificPackagePolicies>[1]
|
||||
): ReturnType<typeof sendGetEndpointSpecificPackagePolicies>;
|
||||
|
||||
assignPolicyToTrustedApps(
|
||||
policyId: string,
|
||||
trustedApps: MaybeImmutable<TrustedApp[]>
|
||||
): Promise<PutTrustedAppUpdateResponse[]>;
|
||||
|
||||
removePolicyFromTrustedApps(
|
||||
policyId: string,
|
||||
trustedApps: MaybeImmutable<TrustedApp[]>
|
||||
): Promise<PutTrustedAppUpdateResponse[]>;
|
||||
}
|
||||
|
||||
const P_MAP_OPTIONS = Object.freeze<pMap.Options>({
|
||||
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<HttpStart>;
|
||||
|
||||
constructor(http: HttpStart) {
|
||||
let ensureListExists: Promise<void>;
|
||||
|
||||
this.getHttpService = async () => {
|
||||
if (!ensureListExists) {
|
||||
ensureListExists = http
|
||||
.post<ExceptionListSchema>(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<ExceptionListItemSchema> {
|
||||
return (await this.getHttpService()).get<ExceptionListItemSchema>(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<GetTrustedAppsListResponse> {
|
||||
const itemListResults = await (
|
||||
await this.getHttpService()
|
||||
).get<FoundExceptionListItemSchema>(`${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<void> {
|
||||
await (
|
||||
await this.getHttpService()
|
||||
).delete<ExceptionListItemSchema>(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<ExceptionListItemSchema>(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<ExceptionListItemSchema>(EXCEPTION_LIST_ITEM_URL, {
|
||||
body: JSON.stringify(
|
||||
updatedTrustedAppToUpdateExceptionListItem(currentExceptionListItem, updatedTrustedApp)
|
||||
),
|
||||
});
|
||||
|
||||
return {
|
||||
data: exceptionListItemToTrustedApp(updatedExceptionListItem),
|
||||
};
|
||||
}
|
||||
|
||||
async getTrustedAppsSummary(_: GetTrustedAppsSummaryRequest) {
|
||||
return (await this.getHttpService()).get<ExceptionListSummarySchema>(
|
||||
`${EXCEPTION_LIST_URL}/summary`,
|
||||
{
|
||||
query: {
|
||||
list_id: ENDPOINT_TRUSTED_APPS_LIST_ID,
|
||||
namespace_type: 'agnostic',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async getPolicyList(options?: Parameters<typeof sendGetEndpointSpecificPackagePolicies>[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<TrustedApp[]>
|
||||
): Promise<PutTrustedAppUpdateResponse[]> {
|
||||
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<TrustedApp[]>
|
||||
): Promise<PutTrustedAppUpdateResponse[]> {
|
||||
return this._handleAssignOrRemovePolicyId('remove', policyId, trustedApps);
|
||||
}
|
||||
|
||||
private _handleAssignOrRemovePolicyId(
|
||||
action: 'assign' | 'remove',
|
||||
policyId: string,
|
||||
trustedApps: MaybeImmutable<TrustedApp[]>
|
||||
): Promise<PutTrustedAppUpdateResponse[]> {
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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 'kibana/public';
|
||||
import {
|
||||
PostTrustedAppCreateRequest,
|
||||
PutTrustedAppUpdateRequest,
|
||||
} from '../../../../../common/endpoint/types';
|
||||
import { HttpRequestValidationError } from './errors';
|
||||
import { sendGetAgentPolicyList } from '../../policy/store/services/ingest';
|
||||
import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../../../../../fleet/common';
|
||||
|
||||
/**
|
||||
* 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<void> => {
|
||||
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);
|
||||
}
|
||||
};
|
|
@ -13,28 +13,18 @@ import { fireEvent } from '@testing-library/dom';
|
|||
import { MiddlewareActionSpyHelper } from '../../../../common/store/test_utils';
|
||||
import {
|
||||
ConditionEntryField,
|
||||
GetTrustedAppsListResponse,
|
||||
NewTrustedApp,
|
||||
OperatingSystem,
|
||||
PostTrustedAppCreateResponse,
|
||||
TrustedApp,
|
||||
} from '../../../../../common/endpoint/types';
|
||||
import { HttpFetchOptions } from 'kibana/public';
|
||||
import {
|
||||
TRUSTED_APPS_GET_API,
|
||||
TRUSTED_APPS_LIST_API,
|
||||
} from '../../../../../common/endpoint/constants';
|
||||
import {
|
||||
GetPackagePoliciesResponse,
|
||||
PACKAGE_POLICY_API_ROUTES,
|
||||
} from '../../../../../../fleet/common';
|
||||
import { EndpointDocGenerator } from '../../../../../common/endpoint/generate_data';
|
||||
import { HttpFetchOptions, HttpFetchOptionsWithPath } from 'kibana/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 { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
|
||||
import { resolvePathVariables } from '../../../../common/utils/resolve_path_variables';
|
||||
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';
|
||||
|
||||
// TODO: remove this mock when feature flag is removed
|
||||
jest.mock('../../../../common/hooks/use_experimental_features');
|
||||
|
@ -59,66 +49,17 @@ describe('When on the Trusted Apps Page', () => {
|
|||
'Add a trusted application to improve performance or alleviate conflicts with other ' +
|
||||
'applications running on your hosts.';
|
||||
|
||||
const generator = new EndpointDocGenerator('policy-list');
|
||||
|
||||
let mockedContext: AppContextTestRender;
|
||||
let history: AppContextTestRender['history'];
|
||||
let coreStart: AppContextTestRender['coreStart'];
|
||||
let waitForAction: MiddlewareActionSpyHelper['waitForAction'];
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let mockedApis: ReturnType<typeof trustedAppsAllHttpMocks>;
|
||||
|
||||
const originalScrollTo = window.scrollTo;
|
||||
const act = reactTestingLibrary.act;
|
||||
|
||||
const getFakeTrustedApp = jest.fn();
|
||||
|
||||
const createListApiResponse = (
|
||||
page: number = 1,
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
per_page: number = 20
|
||||
): GetTrustedAppsListResponse => {
|
||||
return {
|
||||
data: [getFakeTrustedApp()],
|
||||
total: 50, // << Should be a value large enough to fulfill two pages
|
||||
page,
|
||||
per_page,
|
||||
};
|
||||
};
|
||||
|
||||
const mockListApis = (http: AppContextTestRender['coreStart']['http']) => {
|
||||
const currentGetHandler = http.get.getMockImplementation();
|
||||
|
||||
http.get.mockImplementation(async (...args) => {
|
||||
const path = args[0] as unknown as string;
|
||||
// @ts-expect-error TS2352
|
||||
const httpOptions = args[1] as HttpFetchOptions;
|
||||
|
||||
if (path === TRUSTED_APPS_LIST_API) {
|
||||
return createListApiResponse(
|
||||
Number(httpOptions?.query?.page ?? 1),
|
||||
Number(httpOptions?.query?.per_page ?? 20)
|
||||
);
|
||||
}
|
||||
|
||||
if (path === PACKAGE_POLICY_API_ROUTES.LIST_PATTERN) {
|
||||
const policy = generator.generatePolicyPackagePolicy();
|
||||
policy.name = 'test policy A';
|
||||
policy.id = 'abc123';
|
||||
|
||||
const response: GetPackagePoliciesResponse = {
|
||||
items: [policy],
|
||||
page: 1,
|
||||
perPage: 1000,
|
||||
total: 1,
|
||||
};
|
||||
return response;
|
||||
}
|
||||
|
||||
if (currentGetHandler) {
|
||||
return currentGetHandler(...args);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
window.scrollTo = () => {};
|
||||
});
|
||||
|
@ -131,15 +72,15 @@ describe('When on the Trusted Apps Page', () => {
|
|||
mockedContext = createAppRootMockRenderer();
|
||||
getFakeTrustedApp.mockImplementation(
|
||||
(): TrustedApp => ({
|
||||
id: '1111-2222-3333-4444',
|
||||
id: '2d95bec3-b48f-4db7-9622-a2b061cc031d',
|
||||
version: 'abc123',
|
||||
name: 'one app',
|
||||
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: 'a good one',
|
||||
description: 'created by ExceptionListItemGenerator',
|
||||
effectScope: { type: 'global' },
|
||||
entries: [
|
||||
{
|
||||
|
@ -156,6 +97,7 @@ describe('When on the Trusted Apps Page', () => {
|
|||
coreStart = mockedContext.coreStart;
|
||||
(licenseService.isPlatinumPlus as jest.Mock).mockReturnValue(true);
|
||||
waitForAction = mockedContext.middlewareSpy.waitForAction;
|
||||
mockedApis = trustedAppsAllHttpMocks(coreStart.http);
|
||||
render = () => mockedContext.render(<TrustedAppsPage />);
|
||||
reactTestingLibrary.act(() => {
|
||||
history.push('/administration/trusted_apps');
|
||||
|
@ -174,8 +116,6 @@ describe('When on the Trusted Apps Page', () => {
|
|||
return renderResult;
|
||||
};
|
||||
|
||||
beforeEach(() => mockListApis(coreStart.http));
|
||||
|
||||
it('should display subtitle info about trusted apps', async () => {
|
||||
const { getByTestId } = await renderWithListData();
|
||||
expect(getByTestId('header-panel-subtitle').textContent).toEqual(expectedAboutInfo);
|
||||
|
@ -199,7 +139,8 @@ describe('When on the Trusted Apps Page', () => {
|
|||
renderResult = await renderWithListData();
|
||||
|
||||
await act(async () => {
|
||||
(await renderResult.findAllByTestId('trustedAppCard-header-actions-button'))[0].click();
|
||||
// The 3rd Trusted app to be rendered will be a policy specific one
|
||||
(await renderResult.findAllByTestId('trustedAppCard-header-actions-button'))[2].click();
|
||||
});
|
||||
|
||||
act(() => {
|
||||
|
@ -284,7 +225,9 @@ describe('When on the Trusted Apps Page', () => {
|
|||
});
|
||||
|
||||
it('should persist edit params to url', () => {
|
||||
expect(history.location.search).toEqual('?show=edit&id=1111-2222-3333-4444');
|
||||
expect(history.location.search).toEqual(
|
||||
'?show=edit&id=2d95bec3-b48f-4db7-9622-a2b061cc031d'
|
||||
);
|
||||
});
|
||||
|
||||
it('should display the Edit flyout', () => {
|
||||
|
@ -315,14 +258,19 @@ describe('When on the Trusted Apps Page', () => {
|
|||
'addTrustedAppFlyout-createForm-descriptionField'
|
||||
) as HTMLTextAreaElement;
|
||||
|
||||
expect(formNameInput.value).toEqual('one app');
|
||||
expect(formDescriptionInput.value).toEqual('a good one');
|
||||
expect(formNameInput.value).toEqual('Generated Exception (3xnng)');
|
||||
expect(formDescriptionInput.value).toEqual('created by ExceptionListItemGenerator');
|
||||
});
|
||||
|
||||
describe('and when Save is clicked', () => {
|
||||
it('should call the correct api (PUT)', () => {
|
||||
act(() => {
|
||||
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);
|
||||
|
@ -332,33 +280,43 @@ describe('When on the Trusted Apps Page', () => {
|
|||
HttpFetchOptions
|
||||
];
|
||||
|
||||
expect(lastCallToPut[0]).toEqual('/api/endpoint/trusted_apps/1111-2222-3333-4444');
|
||||
expect(lastCallToPut[0]).toEqual('/api/exception_lists/items');
|
||||
|
||||
expect(JSON.parse(lastCallToPut[1].body as string)).toEqual({
|
||||
name: 'one app',
|
||||
os: 'windows',
|
||||
_version: '3o9za',
|
||||
name: 'Generated Exception (3xnng)',
|
||||
description: 'created by ExceptionListItemGenerator',
|
||||
entries: [
|
||||
{
|
||||
field: 'process.executable.caseless',
|
||||
value: 'one/two',
|
||||
field: 'process.hash.md5',
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
value: '1234234659af249ddf3e40864e9fb241',
|
||||
},
|
||||
{
|
||||
field: 'process.executable.caseless',
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
value: '/one/two/three',
|
||||
},
|
||||
],
|
||||
description: 'a good one',
|
||||
effectScope: {
|
||||
type: 'global',
|
||||
},
|
||||
version: 'abc123',
|
||||
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: '2d95bec3-b48f-4db7-9622-a2b061cc031d',
|
||||
meta: {},
|
||||
namespace_type: 'agnostic',
|
||||
type: 'simple',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('and attempting to show Edit panel based on URL params', () => {
|
||||
const TRUSTED_APP_GET_URI = resolvePathVariables(TRUSTED_APPS_GET_API, {
|
||||
id: '9999-edit-8888',
|
||||
});
|
||||
|
||||
const renderAndWaitForGetApi = async () => {
|
||||
// the store action watcher is setup prior to render because `renderWithListData()`
|
||||
// also awaits API calls and this action could be missed.
|
||||
|
@ -381,23 +339,6 @@ describe('When on the Trusted Apps Page', () => {
|
|||
};
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock the API GET for the trusted application
|
||||
const priorMockImplementation = coreStart.http.get.getMockImplementation();
|
||||
coreStart.http.get.mockImplementation(async (...args) => {
|
||||
if ('string' === typeof args[0] && args[0] === TRUSTED_APP_GET_URI) {
|
||||
return {
|
||||
data: {
|
||||
...getFakeTrustedApp(),
|
||||
id: '9999-edit-8888',
|
||||
name: 'one app for edit',
|
||||
},
|
||||
};
|
||||
}
|
||||
if (priorMockImplementation) {
|
||||
return priorMockImplementation(...args);
|
||||
}
|
||||
});
|
||||
|
||||
reactTestingLibrary.act(() => {
|
||||
history.push('/administration/trusted_apps?show=edit&id=9999-edit-8888');
|
||||
});
|
||||
|
@ -406,7 +347,15 @@ describe('When on the Trusted Apps Page', () => {
|
|||
it('should retrieve trusted app via API using url `id`', async () => {
|
||||
renderResult = await renderAndWaitForGetApi();
|
||||
|
||||
expect(coreStart.http.get).toHaveBeenCalledWith(TRUSTED_APP_GET_URI);
|
||||
expect(coreStart.http.get.mock.calls).toContainEqual([
|
||||
EXCEPTION_LIST_ITEM_URL,
|
||||
{
|
||||
query: {
|
||||
item_id: '9999-edit-8888',
|
||||
namespace_type: 'agnostic',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
expect(
|
||||
(
|
||||
|
@ -414,7 +363,7 @@ describe('When on the Trusted Apps Page', () => {
|
|||
'addTrustedAppFlyout-createForm-nameTextField'
|
||||
) as HTMLInputElement
|
||||
).value
|
||||
).toEqual('one app for edit');
|
||||
).toEqual('Generated Exception (u6kh2)');
|
||||
});
|
||||
|
||||
it('should redirect to list and show toast message if `id` is missing from URL', async () => {
|
||||
|
@ -432,14 +381,8 @@ describe('When on the Trusted Apps Page', () => {
|
|||
|
||||
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
|
||||
const priorMockImplementation = coreStart.http.get.getMockImplementation();
|
||||
coreStart.http.get.mockImplementation(async (...args) => {
|
||||
if ('string' === typeof args[0] && args[0] === TRUSTED_APP_GET_URI) {
|
||||
throw new Error('test: api error response');
|
||||
}
|
||||
if (priorMockImplementation) {
|
||||
return priorMockImplementation(...args);
|
||||
}
|
||||
mockedApis.responseProvider.trustedApp.mockImplementation(() => {
|
||||
throw new Error('test: api error response');
|
||||
});
|
||||
|
||||
await renderAndWaitForGetApi();
|
||||
|
@ -486,8 +429,6 @@ describe('When on the Trusted Apps Page', () => {
|
|||
return renderResult;
|
||||
};
|
||||
|
||||
beforeEach(() => mockListApis(coreStart.http));
|
||||
|
||||
it('should display the create flyout', async () => {
|
||||
const { getByTestId } = await renderAndClickAddButton();
|
||||
const flyout = getByTestId('addTrustedAppFlyout');
|
||||
|
@ -505,6 +446,14 @@ describe('When on the Trusted Apps Page', () => {
|
|||
});
|
||||
|
||||
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');
|
||||
});
|
||||
|
@ -524,7 +473,7 @@ describe('When on the Trusted Apps Page', () => {
|
|||
act(() => {
|
||||
fireEvent.click(renderResult.getByTestId('perPolicy'));
|
||||
});
|
||||
expect(renderResult.getByTestId('policy-abc123'));
|
||||
expect(renderResult.getByTestId('policy-ddf6570b-9175-4a6d-b288-61a09771c647'));
|
||||
resetEnv();
|
||||
});
|
||||
|
||||
|
@ -582,39 +531,33 @@ describe('When on the Trusted Apps Page', () => {
|
|||
|
||||
describe('and the Flyout Add button is clicked', () => {
|
||||
let renderResult: ReturnType<AppContextTestRender['render']>;
|
||||
let resolveHttpPost: (response?: PostTrustedAppCreateResponse) => void;
|
||||
let httpPostBody: string;
|
||||
let rejectHttpPost: (response: Error) => void;
|
||||
let releasePostCreateApi: () => void;
|
||||
|
||||
beforeEach(async () => {
|
||||
// Mock the http.post() call and expose `resolveHttpPost()` method so that
|
||||
// we can control when the API call response is returned, which will allow us
|
||||
// to test the UI behaviours while the API call is in flight
|
||||
coreStart.http.post.mockImplementation(
|
||||
// @ts-expect-error TS2345
|
||||
async (_, options: HttpFetchOptions) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
httpPostBody = options.body as string;
|
||||
resolveHttpPost = resolve;
|
||||
rejectHttpPost = reject;
|
||||
});
|
||||
}
|
||||
// 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;
|
||||
})
|
||||
);
|
||||
|
||||
renderResult = 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(() => resolveHttpPost());
|
||||
afterEach(() => releasePostCreateApi());
|
||||
|
||||
it('should display info about Trusted Apps', async () => {
|
||||
expect(renderResult.getByTestId('addTrustedAppFlyout-about').textContent).toEqual(
|
||||
|
@ -627,7 +570,7 @@ describe('When on the Trusted Apps Page', () => {
|
|||
(renderResult.getByTestId('addTrustedAppFlyout-cancelButton') as HTMLButtonElement)
|
||||
.disabled
|
||||
).toBe(true);
|
||||
resolveHttpPost();
|
||||
releasePostCreateApi();
|
||||
});
|
||||
|
||||
it('should hide the dialog close button', async () => {
|
||||
|
@ -644,23 +587,13 @@ describe('When on the Trusted Apps Page', () => {
|
|||
|
||||
describe('and if create was successful', () => {
|
||||
beforeEach(async () => {
|
||||
const successCreateApiResponse: PostTrustedAppCreateResponse = {
|
||||
data: {
|
||||
...(JSON.parse(httpPostBody) as NewTrustedApp),
|
||||
id: '1',
|
||||
version: 'abc123',
|
||||
created_at: '2020-09-16T14:09:45.484Z',
|
||||
created_by: 'kibana',
|
||||
updated_at: '2021-01-04T13:55:00.561Z',
|
||||
updated_by: 'me',
|
||||
},
|
||||
};
|
||||
await reactTestingLibrary.act(async () => {
|
||||
const serverResponseAction = waitForAction(
|
||||
'trustedAppCreationSubmissionResourceStateChanged'
|
||||
);
|
||||
|
||||
coreStart.http.get.mockClear();
|
||||
resolveHttpPost(successCreateApiResponse);
|
||||
releasePostCreateApi();
|
||||
await serverResponseAction;
|
||||
});
|
||||
});
|
||||
|
@ -671,33 +604,47 @@ describe('When on the Trusted Apps Page', () => {
|
|||
|
||||
it('should show success toast notification', () => {
|
||||
expect(coreStart.notifications.toasts.addSuccess.mock.calls[0][0]).toEqual({
|
||||
text: '"one app" has been added to the Trusted Applications list.',
|
||||
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() === TRUSTED_APPS_LIST_API
|
||||
(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: Error & { body?: { message: string } } = new Error(
|
||||
'Bad call'
|
||||
);
|
||||
failedCreateApiResponse.body = {
|
||||
message: 'bad call',
|
||||
};
|
||||
const failedCreateApiResponse = new ServerErrorResponseBodyMock();
|
||||
|
||||
mockedApis.responseProvider.trustedAppCreate.mockImplementation(() => {
|
||||
throw failedCreateApiResponse;
|
||||
});
|
||||
|
||||
await reactTestingLibrary.act(async () => {
|
||||
const serverResponseAction = waitForAction(
|
||||
'trustedAppCreationSubmissionResourceStateChanged'
|
||||
'trustedAppCreationSubmissionResourceStateChanged',
|
||||
{
|
||||
validate({ payload }) {
|
||||
return isFailedResourceState(payload.newState);
|
||||
},
|
||||
}
|
||||
);
|
||||
coreStart.http.get.mockClear();
|
||||
rejectHttpPost(failedCreateApiResponse);
|
||||
|
||||
releasePostCreateApi();
|
||||
await serverResponseAction;
|
||||
});
|
||||
});
|
||||
|
@ -773,54 +720,31 @@ describe('When on the Trusted Apps Page', () => {
|
|||
});
|
||||
|
||||
describe('and there are no trusted apps', () => {
|
||||
const releaseExistsResponse: jest.MockedFunction<() => Promise<GetTrustedAppsListResponse>> =
|
||||
jest.fn(async () => {
|
||||
return {
|
||||
data: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
per_page: 1,
|
||||
};
|
||||
});
|
||||
const releaseListResponse: jest.MockedFunction<() => Promise<GetTrustedAppsListResponse>> =
|
||||
jest.fn(async () => {
|
||||
return {
|
||||
data: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
per_page: 20,
|
||||
};
|
||||
});
|
||||
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(() => {
|
||||
const priorMockImplementation = coreStart.http.get.getMockImplementation();
|
||||
// @ts-expect-error TS7006
|
||||
coreStart.http.get.mockImplementation((path, options) => {
|
||||
if (path === TRUSTED_APPS_LIST_API) {
|
||||
const { page, per_page: perPage } = options.query as { page: number; per_page: number };
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
if (path === PACKAGE_POLICY_API_ROUTES.LIST_PATTERN) {
|
||||
const policy = generator.generatePolicyPackagePolicy();
|
||||
policy.name = 'test policy A';
|
||||
policy.id = 'abc123';
|
||||
|
||||
const response: GetPackagePoliciesResponse = {
|
||||
items: [policy],
|
||||
page: 1,
|
||||
perPage: 1000,
|
||||
total: 1,
|
||||
};
|
||||
return response;
|
||||
}
|
||||
if (priorMockImplementation) {
|
||||
return priorMockImplementation(path);
|
||||
if (page === 1 && perPage === 1) {
|
||||
return releaseExistsResponse();
|
||||
} else {
|
||||
return releaseListResponse();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -831,8 +755,6 @@ describe('When on the Trusted Apps Page', () => {
|
|||
});
|
||||
|
||||
it('should show a loader until trusted apps existence can be confirmed', async () => {
|
||||
// Make the call that checks if Trusted Apps exists not respond back
|
||||
releaseExistsResponse.mockImplementationOnce(() => new Promise(() => {}));
|
||||
const renderResult = render();
|
||||
expect(await renderResult.findByTestId('trustedAppsListLoader')).not.toBeNull();
|
||||
});
|
||||
|
@ -851,14 +773,14 @@ describe('When on the Trusted Apps Page', () => {
|
|||
await waitForAction('trustedAppsExistStateChanged');
|
||||
});
|
||||
expect(await renderResult.findByTestId('trustedAppEmptyState')).not.toBeNull();
|
||||
releaseListResponse.mockResolvedValueOnce({
|
||||
data: [getFakeTrustedApp()],
|
||||
releaseListResponse.mockReturnValueOnce({
|
||||
data: [mockedApis.responseProvider.trustedApp({ query: {} } as HttpFetchOptionsWithPath)],
|
||||
total: 1,
|
||||
page: 1,
|
||||
per_page: 20,
|
||||
});
|
||||
releaseExistsResponse.mockResolvedValueOnce({
|
||||
data: [getFakeTrustedApp()],
|
||||
releaseExistsResponse.mockReturnValueOnce({
|
||||
data: [mockedApis.responseProvider.trustedApp({ query: {} } as HttpFetchOptionsWithPath)],
|
||||
total: 1,
|
||||
page: 1,
|
||||
per_page: 1,
|
||||
|
@ -875,14 +797,14 @@ describe('When on the Trusted Apps Page', () => {
|
|||
});
|
||||
|
||||
it('should should show empty prompt once the last trusted app entry is deleted', async () => {
|
||||
releaseListResponse.mockResolvedValueOnce({
|
||||
data: [getFakeTrustedApp()],
|
||||
releaseListResponse.mockReturnValueOnce({
|
||||
data: [mockedApis.responseProvider.trustedApp({ query: {} } as HttpFetchOptionsWithPath)],
|
||||
total: 1,
|
||||
page: 1,
|
||||
per_page: 20,
|
||||
});
|
||||
releaseExistsResponse.mockResolvedValueOnce({
|
||||
data: [getFakeTrustedApp()],
|
||||
releaseExistsResponse.mockReturnValueOnce({
|
||||
data: [mockedApis.responseProvider.trustedApp({ query: {} } as HttpFetchOptionsWithPath)],
|
||||
total: 1,
|
||||
page: 1,
|
||||
per_page: 1,
|
||||
|
@ -896,19 +818,6 @@ describe('When on the Trusted Apps Page', () => {
|
|||
|
||||
expect(await renderResult.findByTestId('trustedAppsListPageContent')).not.toBeNull();
|
||||
|
||||
releaseListResponse.mockResolvedValueOnce({
|
||||
data: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
per_page: 20,
|
||||
});
|
||||
releaseExistsResponse.mockResolvedValueOnce({
|
||||
data: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
per_page: 1,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
mockedContext.store.dispatch({
|
||||
type: 'trustedAppsListDataOutdated',
|
||||
|
@ -931,7 +840,6 @@ describe('When on the Trusted Apps Page', () => {
|
|||
describe('and the search is dispatched', () => {
|
||||
let renderResult: ReturnType<AppContextTestRender['render']>;
|
||||
beforeEach(async () => {
|
||||
mockListApis(coreStart.http);
|
||||
reactTestingLibrary.act(() => {
|
||||
history.push('/administration/trusted_apps?filter=test');
|
||||
});
|
||||
|
@ -956,28 +864,6 @@ describe('When on the Trusted Apps Page', () => {
|
|||
describe('and the back button is present', () => {
|
||||
let renderResult: ReturnType<AppContextTestRender['render']>;
|
||||
beforeEach(async () => {
|
||||
// Ensure implementation is defined before render to avoid undefined responses from hidden api calls
|
||||
const priorMockImplementation = coreStart.http.get.getMockImplementation();
|
||||
// @ts-expect-error TS7006
|
||||
coreStart.http.get.mockImplementation((path, options) => {
|
||||
if (path === PACKAGE_POLICY_API_ROUTES.LIST_PATTERN) {
|
||||
const policy = generator.generatePolicyPackagePolicy();
|
||||
policy.name = 'test policy A';
|
||||
policy.id = 'abc123';
|
||||
|
||||
const response: GetPackagePoliciesResponse = {
|
||||
items: [policy],
|
||||
page: 1,
|
||||
perPage: 1000,
|
||||
total: 1,
|
||||
};
|
||||
return response;
|
||||
}
|
||||
if (priorMockImplementation) {
|
||||
return priorMockImplementation(path);
|
||||
}
|
||||
});
|
||||
|
||||
renderResult = render();
|
||||
await act(async () => {
|
||||
await waitForAction('trustedAppsListResourceStateChanged');
|
||||
|
|
|
@ -7,13 +7,7 @@
|
|||
|
||||
/* eslint-disable max-classes-per-file */
|
||||
|
||||
export class EndpointError extends Error {
|
||||
constructor(message: string, public readonly meta?: unknown) {
|
||||
super(message);
|
||||
// For debugging - capture name of subclasses
|
||||
this.name = this.constructor.name;
|
||||
}
|
||||
}
|
||||
import { EndpointError } from '../../common/endpoint/errors';
|
||||
|
||||
export class NotFoundError extends EndpointError {}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ import { isEmptyManifestDiff, Manifest } from './manifest';
|
|||
import { InvalidInternalManifestError } from '../../services/artifacts/errors';
|
||||
import { ManifestManager } from '../../services';
|
||||
import { wrapErrorIfNeeded } from '../../utils';
|
||||
import { EndpointError } from '../../errors';
|
||||
import { EndpointError } from '../../../../common/endpoint/errors';
|
||||
|
||||
export const ManifestTaskConstants = {
|
||||
TIMEOUT: '1m',
|
||||
|
|
|
@ -34,7 +34,8 @@ import { findAgentIdsByStatus } from './support/agent_status';
|
|||
import { EndpointAppContextService } from '../../endpoint_app_context_services';
|
||||
import { fleetAgentStatusToEndpointHostStatus } from '../../utils';
|
||||
import { queryResponseToHostListResult } from './support/query_strategies';
|
||||
import { EndpointError, NotFoundError } from '../../errors';
|
||||
import { NotFoundError } from '../../errors';
|
||||
import { EndpointError } from '../../../../common/endpoint/errors';
|
||||
import { EndpointHostUnEnrolledError } from '../../services/metadata';
|
||||
import { CustomHttpRequestError } from '../../../utils/custom_http_request_error';
|
||||
import { GetMetadataListRequestQuery } from '../../../../common/endpoint/schema/metadata';
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EndpointError } from '../../errors';
|
||||
import { EndpointError } from '../../../../common/endpoint/errors';
|
||||
|
||||
/**
|
||||
* Indicates that the internal manifest that is managed by ManifestManager is invalid or contains
|
||||
|
|
|
@ -38,8 +38,8 @@ import {
|
|||
|
||||
import { ManifestManager } from './manifest_manager';
|
||||
import { EndpointArtifactClientInterface } from '../artifact_client';
|
||||
import { EndpointError } from '../../../errors';
|
||||
import { InvalidInternalManifestError } from '../errors';
|
||||
import { EndpointError } from '../../../../../common/endpoint/errors';
|
||||
|
||||
const getArtifactObject = (artifact: InternalArtifactSchema) =>
|
||||
JSON.parse(Buffer.from(artifact.body!, 'base64').toString());
|
||||
|
|
|
@ -38,7 +38,7 @@ import { ManifestClient } from '../manifest_client';
|
|||
import { ExperimentalFeatures } from '../../../../../common/experimental_features';
|
||||
import { InvalidInternalManifestError } from '../errors';
|
||||
import { wrapErrorIfNeeded } from '../../../utils';
|
||||
import { EndpointError } from '../../../errors';
|
||||
import { EndpointError } from '../../../../../common/endpoint/errors';
|
||||
|
||||
interface ArtifactsBuildResult {
|
||||
defaultArtifacts: InternalArtifactCompleteSchema[];
|
||||
|
|
|
@ -21,10 +21,10 @@ import {
|
|||
getESQueryHostMetadataByFleetAgentIds,
|
||||
buildUnitedIndexQuery,
|
||||
} from '../../routes/metadata/query_builders';
|
||||
import { EndpointError } from '../../errors';
|
||||
import { HostMetadata } from '../../../../common/endpoint/types';
|
||||
import { Agent } from '../../../../../fleet/common';
|
||||
import { AgentPolicyServiceInterface } from '../../../../../fleet/server/services';
|
||||
import { EndpointError } from '../../../../common/endpoint/errors';
|
||||
|
||||
describe('EndpointMetadataService', () => {
|
||||
let testMockedContext: EndpointMetadataServiceTestContextMock;
|
||||
|
|
|
@ -51,12 +51,12 @@ import {
|
|||
fleetAgentStatusToEndpointHostStatus,
|
||||
wrapErrorIfNeeded,
|
||||
} from '../../utils';
|
||||
import { EndpointError } from '../../errors';
|
||||
import { createInternalReadonlySoClient } from '../../utils/create_internal_readonly_so_client';
|
||||
import { METADATA_UNITED_INDEX } from '../../../../common/endpoint/constants';
|
||||
import { getAllEndpointPackagePolicies } from '../../routes/metadata/support/endpoint_package_policies';
|
||||
import { getAgentStatus } from '../../../../../fleet/common/services/agent_status';
|
||||
import { GetMetadataListRequestQuery } from '../../../../common/endpoint/schema/metadata';
|
||||
import { EndpointError } from '../../../../common/endpoint/errors';
|
||||
|
||||
type AgentPolicyWithPackagePolicies = Omit<AgentPolicy, 'package_policies'> & {
|
||||
package_policies: PackagePolicy[];
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
|
||||
/* eslint-disable max-classes-per-file */
|
||||
|
||||
import { EndpointError, NotFoundError } from '../../errors';
|
||||
import { NotFoundError } from '../../errors';
|
||||
import { EndpointError } from '../../../../common/endpoint/errors';
|
||||
|
||||
export class EndpointHostNotFoundError extends NotFoundError {}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { KibanaRequest, SavedObjectsClientContract, SavedObjectsServiceStart } from 'kibana/server';
|
||||
import { EndpointError } from '../errors';
|
||||
import { EndpointError } from '../../../common/endpoint/errors';
|
||||
|
||||
type SavedObjectsClientContractKeys = keyof SavedObjectsClientContract;
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EndpointError } from '../errors';
|
||||
import { EndpointError } from '../../../common/endpoint/errors';
|
||||
|
||||
/**
|
||||
* Will wrap the given Error with `EndpointError`, which will help getting a good picture of where in
|
||||
|
|
|
@ -54,4 +54,3 @@ export type { ConfigType, PluginSetup, PluginStart };
|
|||
export { Plugin };
|
||||
export { AppClient };
|
||||
export type { SecuritySolutionApiRequestHandlerContext } from './types';
|
||||
export { EndpointError } from './endpoint/errors';
|
||||
|
|
|
@ -12,7 +12,6 @@ import {
|
|||
metadataCurrentIndexPattern,
|
||||
metadataTransformPrefix,
|
||||
} from '../../../plugins/security_solution/common/endpoint/constants';
|
||||
import { EndpointError } from '../../../plugins/security_solution/server';
|
||||
import {
|
||||
deleteIndexedHostsAndAlerts,
|
||||
IndexedHostsAndAlertsResponse,
|
||||
|
@ -22,6 +21,7 @@ import { TransformConfigUnion } from '../../../plugins/transform/common/types/tr
|
|||
import { GetTransformsResponseSchema } from '../../../plugins/transform/common/api_schemas/transforms';
|
||||
import { catchAndWrapError } from '../../../plugins/security_solution/server/endpoint/utils';
|
||||
import { installOrUpgradeEndpointFleetPackage } from '../../../plugins/security_solution/common/endpoint/data_loaders/setup_fleet_for_endpoint';
|
||||
import { EndpointError } from '../../../plugins/security_solution/common/endpoint/errors';
|
||||
|
||||
export class EndpointTestResources extends FtrService {
|
||||
private readonly esClient = this.ctx.getService('es');
|
||||
|
|
|
@ -24,7 +24,7 @@ import { Immutable } from '../../../plugins/security_solution/common/endpoint/ty
|
|||
|
||||
// NOTE: import path below should be the deep path to the actual module - else we get CI errors
|
||||
import { pkgKeyFromPackageInfo } from '../../../plugins/fleet/public/services/pkg_key_from_package_info';
|
||||
import { EndpointError } from '../../../plugins/security_solution/server';
|
||||
import { EndpointError } from '../../../plugins/security_solution/common/endpoint/errors';
|
||||
|
||||
const INGEST_API_ROOT = '/api/fleet';
|
||||
const INGEST_API_AGENT_POLICIES = `${INGEST_API_ROOT}/agent_policies`;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue