Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
51f96f7
Basic form for tursted apps create
paul-tavares Sep 3, 2020
db38e71
Initial component scafolding for a LogicalConditionBuilder component
paul-tavares Sep 3, 2020
83d7a95
Persist user form element input
paul-tavares Sep 8, 2020
036b315
small comment
paul-tavares Sep 8, 2020
a0e4cb6
Condition Entry initial implementation
paul-tavares Sep 8, 2020
383c94f
Ability to add/remove entries
paul-tavares Sep 8, 2020
dc838d0
Show values for Field dropdown
paul-tavares Sep 8, 2020
a14a256
updates to condition items persist at top level
paul-tavares Sep 8, 2020
3c425c3
adjustment to markup in condition builder
paul-tavares Sep 8, 2020
c3055fc
Merge remote-tracking branch 'upstream/master' into task/EMT-691-trus…
paul-tavares Sep 9, 2020
e545eec
Make all Flyouts in Administation area show above the SIEM top-level …
paul-tavares Sep 9, 2020
5265092
Add Trusted App button + flyout display
paul-tavares Sep 9, 2020
f1ec25d
Moved and renamed Trusted Apps ListPaginationParams
paul-tavares Sep 9, 2020
b6ad9a1
Added support for `show` url param in normalizing params
paul-tavares Sep 9, 2020
a0db8ad
Drive flyout via URL param `show`
paul-tavares Sep 9, 2020
03d2a36
Add header and footer to craete flyout
paul-tavares Sep 9, 2020
b04d51e
Rename of components
paul-tavares Sep 9, 2020
f054c66
Added `onChange` to Trusted App form + initial basic validations
paul-tavares Sep 9, 2020
f566037
Fix react warning on missing `key`
paul-tavares Sep 9, 2020
583d933
Flyout UI interactions for clicking the save (create) button
paul-tavares Sep 10, 2020
1525426
Plugged save action to store, middleware and API
paul-tavares Sep 10, 2020
3802b03
Show API error on Form
paul-tavares Sep 10, 2020
121970e
Show toast on successful create a new trusted app entry
paul-tavares Sep 10, 2020
85b97e7
fix: clear createView state on exiting flyout
paul-tavares Sep 10, 2020
367bf94
Reuse OS_TITLES from list ++ fixed some types
paul-tavares Sep 10, 2020
db518a9
Fix more types
paul-tavares Sep 10, 2020
f291aad
Fix some more types
paul-tavares Sep 10, 2020
dae9d71
Fix some tests
paul-tavares Sep 14, 2020
3d9b128
Merge remote-tracking branch 'upstream/master' into task/EMT-691-trus…
paul-tavares Sep 14, 2020
406b474
Fix UI UT for trusted app list page
paul-tavares Sep 14, 2020
1d3856e
Refresh list after successful Create
paul-tavares Sep 14, 2020
d1bc53e
Fix TypeScript errors
paul-tavares Sep 14, 2020
ea70142
Fix test case to ensure htmlIdGenerator is mocked
paul-tavares Sep 14, 2020
9d5982d
Trusted Apps List Page test plan for the create flyout
paul-tavares Sep 14, 2020
7e59185
Fix Trusted App server tests
paul-tavares Sep 15, 2020
9cec20a
Added two missing tests to the Trusted Apps schema
paul-tavares Sep 15, 2020
8a7e235
Some tests for Trusted Apps Create Flyout
paul-tavares Sep 15, 2020
0daa736
Additional tests for the create flyout
paul-tavares Sep 15, 2020
0bd7ac1
Added test ids + more tests
paul-tavares Sep 15, 2020
2244329
Merge remote-tracking branch 'upstream/master' into task/EMT-691-trus…
paul-tavares Sep 15, 2020
1546f90
More tests
paul-tavares Sep 15, 2020
abe027e
Done with tests
paul-tavares Sep 16, 2020
c5dd93e
Merge remote-tracking branch 'upstream/master' into task/EMT-691-trus…
paul-tavares Sep 16, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const telemetryIndexPattern = 'metrics-endpoint.telemetry-*';
export const LIMITED_CONCURRENCY_ENDPOINT_ROUTE_TAG = 'endpoint:limited-concurrency';
export const LIMITED_CONCURRENCY_ENDPOINT_COUNT = 100;

export const TRUSTED_APPS_SUPPORTED_OS_TYPES: readonly string[] = ['macos', 'windows', 'linux'];
export const TRUSTED_APPS_LIST_API = '/api/endpoint/trusted_apps';
export const TRUSTED_APPS_CREATE_API = '/api/endpoint/trusted_apps';
export const TRUSTED_APPS_DELETE_API = '/api/endpoint/trusted_apps/{id}';
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe('When invoking Trusted Apps Schema', () => {
os: 'windows',
entries: [
{
field: 'path',
field: 'process.path',
type: 'match',
operator: 'included',
value: 'c:/programs files/Anti-Virus',
Expand Down Expand Up @@ -111,14 +111,6 @@ describe('When invoking Trusted Apps Schema', () => {
expect(body.validate(bodyMsg)).toStrictEqual(bodyMsg);
});

it('should validate `description` to be non-empty if defined', () => {
const bodyMsg = {
...getCreateTrustedAppItem(),
description: '',
};
expect(() => body.validate(bodyMsg)).toThrow();
});

it('should validate `os` to to only accept known values', () => {
const bodyMsg = {
...getCreateTrustedAppItem(),
Expand Down Expand Up @@ -202,7 +194,7 @@ describe('When invoking Trusted Apps Schema', () => {
};
expect(() => body.validate(bodyMsg2)).toThrow();

['hash', 'path'].forEach((field) => {
['process.hash.*', 'process.path'].forEach((field) => {
const bodyMsg3 = {
...getCreateTrustedAppItem(),
entries: [
Expand All @@ -217,9 +209,55 @@ describe('When invoking Trusted Apps Schema', () => {
});
});

it.todo('should validate `entry.type` is limited to known values');
it('should validate `entry.type` is limited to known values', () => {
const bodyMsg = {
...getCreateTrustedAppItem(),
entries: [
{
...getTrustedAppItemEntryItem(),
type: 'invalid',
},
],
};
expect(() => body.validate(bodyMsg)).toThrow();

// Allow `match`
const bodyMsg2 = {
...getCreateTrustedAppItem(),
entries: [
{
...getTrustedAppItemEntryItem(),
type: 'match',
},
],
};
expect(() => body.validate(bodyMsg2)).not.toThrow();
});

it('should validate `entry.operator` is limited to known values', () => {
const bodyMsg = {
...getCreateTrustedAppItem(),
entries: [
{
...getTrustedAppItemEntryItem(),
operator: 'invalid',
},
],
};
expect(() => body.validate(bodyMsg)).toThrow();

it.todo('should validate `entry.operator` is limited to known values');
// Allow `match`
const bodyMsg2 = {
...getCreateTrustedAppItem(),
entries: [
{
...getTrustedAppItemEntryItem(),
operator: 'included',
},
],
};
expect(() => body.validate(bodyMsg2)).not.toThrow();
});

it('should validate `entry.value` required', () => {
const { value, ...entry } = getTrustedAppItemEntryItem();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ export const GetTrustedAppsRequestSchema = {
export const PostTrustedAppCreateRequestSchema = {
body: schema.object({
name: schema.string({ minLength: 1 }),
description: schema.maybe(schema.string({ minLength: 1 })),
description: schema.maybe(schema.string({ minLength: 0, defaultValue: '' })),
os: schema.oneOf([schema.literal('linux'), schema.literal('macos'), schema.literal('windows')]),
entries: schema.arrayOf(
schema.object({
field: schema.oneOf([schema.literal('hash'), schema.literal('path')]),
field: schema.oneOf([schema.literal('process.hash.*'), schema.literal('process.path')]),
type: schema.literal('match'),
operator: schema.literal('included'),
value: schema.string({ minLength: 1 }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,17 @@ export interface PostTrustedAppCreateResponse {
data: TrustedApp;
}

interface MacosLinuxConditionEntry {
field: 'hash' | 'path';
export interface MacosLinuxConditionEntry {
field: 'process.hash.*' | 'process.path';
type: 'match';
operator: 'included';
value: string;
}

type WindowsConditionEntry =
export type WindowsConditionEntry =
| MacosLinuxConditionEntry
| (Omit<MacosLinuxConditionEntry, 'field'> & {
field: 'signer';
field: 'process.code_signature';
});

/** Type for a new Trusted App Entry */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
import { AdministrationSubTab } from '../types';
import { appendSearch } from '../../common/components/link_to/helpers';
import { EndpointIndexUIQueryParams } from '../pages/endpoint_hosts/types';
import { TrustedAppsUrlParams } from '../pages/trusted_apps/types';

// Taken from: https://github.com/microsoft/TypeScript/issues/12936#issuecomment-559034150
type ExactKeys<T1, T2> = Exclude<keyof T1, keyof T2> extends never ? T1 : never;
Expand Down Expand Up @@ -89,18 +90,16 @@ export const getPolicyDetailPath = (policyId: string, search?: string) => {
})}${appendSearch(search)}`;
};

interface ListPaginationParams {
page_index: number;
page_size: number;
}

const isDefaultOrMissing = (value: number | undefined, defaultValue: number) => {
const isDefaultOrMissing = (
value: number | string | undefined,
defaultValue: number | undefined
) => {
return value === undefined || value === defaultValue;
};

const normalizeListPaginationParams = (
params?: Partial<ListPaginationParams>
): Partial<ListPaginationParams> => {
params?: Partial<TrustedAppsUrlParams>
): Partial<TrustedAppsUrlParams> => {
if (params) {
return {
...(!isDefaultOrMissing(params.page_index, MANAGEMENT_DEFAULT_PAGE)
Expand All @@ -109,13 +108,19 @@ const normalizeListPaginationParams = (
...(!isDefaultOrMissing(params.page_size, MANAGEMENT_DEFAULT_PAGE_SIZE)
? { page_size: params.page_size }
: {}),
...(!isDefaultOrMissing(params.show, undefined) ? { show: params.show } : {}),
};
} else {
return {};
}
};

const extractFirstParamValue = (query: querystring.ParsedUrlQuery, key: string): string => {
/**
* Given an object with url params, and a given key, return back only the first param value (case multiples were defined)
* @param query
* @param key
*/
export const extractFirstParamValue = (query: querystring.ParsedUrlQuery, key: string): string => {
const value = query[key];

return Array.isArray(value) ? value[value.length - 1] : value;
Expand All @@ -135,12 +140,12 @@ const extractPageSize = (query: querystring.ParsedUrlQuery): number => {

export const extractListPaginationParams = (
query: querystring.ParsedUrlQuery
): ListPaginationParams => ({
): TrustedAppsUrlParams => ({
page_index: extractPageIndex(query),
page_size: extractPageSize(query),
});

export const getTrustedAppsListPath = (params?: Partial<ListPaginationParams>): string => {
export const getTrustedAppsListPath = (params?: Partial<TrustedAppsUrlParams>): string => {
const path = generatePath(MANAGEMENT_ROUTING_TRUSTED_APPS_PATH, {
tabName: AdministrationSubTab.trustedApps,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/
import React, { FC, memo } from 'react';
import { EuiPanel, EuiSpacer, CommonProps } from '@elastic/eui';
import styled from 'styled-components';
import { SecurityPageName } from '../../../common/constants';
import { WrapperPage } from '../../common/components/wrapper_page';
import { HeaderPage } from '../../common/components/header_page';
Expand All @@ -14,6 +15,13 @@ import { AdministrationSubTab } from '../types';
import { ENDPOINTS_TAB, TRUSTED_APPS_TAB, BETA_BADGE_LABEL } from '../common/translations';
import { getEndpointListPath, getTrustedAppsListPath } from '../common/routing';

/** Ensure that all flyouts z-index in Administation area show the flyout header */
const EuiPanelStyled = styled(EuiPanel)`
.euiFlyout {
z-index: ${({ theme }) => theme.eui.euiZNavigation + 1};
}
`;

interface AdministrationListPageProps {
beta: boolean;
title: React.ReactNode;
Expand Down Expand Up @@ -54,7 +62,7 @@ export const AdministrationListPage: FC<AdministrationListPageProps & CommonProp

<EuiSpacer />

<EuiPanel>{children}</EuiPanel>
<EuiPanelStyled>{children}</EuiPanelStyled>

<SpyRoute pageName={SecurityPageName.administration} />
</WrapperPage>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@
*/

import { HttpStart } from 'kibana/public';
import { TRUSTED_APPS_LIST_API } from '../../../../../common/endpoint/constants';
import {
TRUSTED_APPS_CREATE_API,
TRUSTED_APPS_LIST_API,
} from '../../../../../common/endpoint/constants';
import {
GetTrustedListAppsResponse,
GetTrustedAppsListRequest,
PostTrustedAppCreateRequest,
PostTrustedAppCreateResponse,
} from '../../../../../common/endpoint/types/trusted_apps';

export interface TrustedAppsService {
getTrustedAppsList(request: GetTrustedAppsListRequest): Promise<GetTrustedListAppsResponse>;
createTrustedApp(request: PostTrustedAppCreateRequest): Promise<PostTrustedAppCreateResponse>;
}

export class TrustedAppsHttpService implements TrustedAppsService {
Expand All @@ -23,4 +29,10 @@ export class TrustedAppsHttpService implements TrustedAppsService {
query: request,
});
}

async createTrustedApp(request: PostTrustedAppCreateRequest) {
return this.http.post<PostTrustedAppCreateResponse>(TRUSTED_APPS_CREATE_API, {
body: JSON.stringify(request),
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { TrustedApp } from '../../../../../common/endpoint/types/trusted_apps';
import { NewTrustedApp, TrustedApp } from '../../../../../common/endpoint/types/trusted_apps';
import { AsyncResourceState } from '.';
import { TrustedAppsUrlParams } from '../types';
import { ServerApiError } from '../../../../common/types';

export interface PaginationInfo {
index: number;
Expand All @@ -18,10 +20,34 @@ export interface TrustedAppsListData {
paginationInfo: PaginationInfo;
}

/** Store State when an API request has been sent to create a new trusted app entry */
export interface TrustedAppCreatePending {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if there's a way to group these states similar to the List resource. I know it's not entirely the same since we're creating a resource as opposed to loading it.

type: 'pending';
data: NewTrustedApp;
}

/** Store State when creation of a new Trusted APP entry was successful */
export interface TrustedAppCreateSuccess {
type: 'success';
data: TrustedApp;
}

/** Store State when creation of a new Trusted App Entry failed */
export interface TrustedAppCreateFailure {
type: 'failure';
data: ServerApiError;
}

export interface TrustedAppsListPageState {
listView: {
currentListResourceState: AsyncResourceState<TrustedAppsListData>;
currentPaginationInfo: PaginationInfo;
show: TrustedAppsUrlParams['show'] | undefined;
};
createView:
| undefined
| TrustedAppCreatePending
| TrustedAppCreateSuccess
| TrustedAppCreateFailure;
active: boolean;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import {
TrustedAppCreatePending,
TrustedAppsListPageState,
TrustedAppCreateFailure,
TrustedAppCreateSuccess,
} from './trusted_apps_list_page_state';
import {
Immutable,
NewTrustedApp,
WindowsConditionEntry,
} from '../../../../../common/endpoint/types';
import { TRUSTED_APPS_SUPPORTED_OS_TYPES } from '../../../../../common/endpoint/constants';

type CreateViewPossibleStates =
| TrustedAppsListPageState['createView']
| Immutable<TrustedAppsListPageState['createView']>;

export const isTrustedAppCreatePendingState = (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this related to what bohdan was talking about before about having selectors return types?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes :)

Type guards are a runtime (executable code) function whose purpose is to ensure we are working with the correct type. In TS, it helps tell it that if this function evaluates to true, then the value being evaluated (data) is of a given type (note the : data is TrustedAppCreateSuccess in the return value for this function's signature.

data: CreateViewPossibleStates
): data is TrustedAppCreatePending => {
return data?.type === 'pending';
};

export const isTrustedAppCreateSuccessState = (
data: CreateViewPossibleStates
): data is TrustedAppCreateSuccess => {
return data?.type === 'success';
};

export const isTrustedAppCreateFailureState = (
data: CreateViewPossibleStates
): data is TrustedAppCreateFailure => {
return data?.type === 'failure';
};

export const isWindowsTrustedApp = <T extends NewTrustedApp = NewTrustedApp>(
trustedApp: T
): trustedApp is T & { os: 'windows' } => {
return trustedApp.os === 'windows';
};

export const isWindowsTrustedAppCondition = (condition: {
field: string;
}): condition is WindowsConditionEntry => {
return condition.field === 'process.code_signature' || true;
};

export const isTrustedAppSupportedOs = (os: string): os is NewTrustedApp['os'] =>
TRUSTED_APPS_SUPPORTED_OS_TYPES.includes(os);
Loading