Skip to content

Commit

Permalink
Merge branch 'master' into feature/olm-trusted_apps_hash_UI_validatio…
Browse files Browse the repository at this point in the history
…n-589
  • Loading branch information
kibanamachine authored Mar 22, 2021
2 parents 3f2cf5f + 35af8a9 commit 5532d28
Show file tree
Hide file tree
Showing 10 changed files with 214 additions and 80 deletions.
2 changes: 1 addition & 1 deletion docs/setup/settings.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ running behind a proxy. Use the <<server-rewriteBasePath, `server.rewriteBasePat
if it should remove the basePath from requests it receives, and to prevent a
deprecation warning at startup. This setting cannot end in a slash (`/`).

|[[server-publicBaseUrl]] `server.publicBaseUrl:`
|[[server-publicBaseUrl]] `server.publicBaseUrl:` {ess-icon}
| The publicly available URL that end-users access Kibana at. Must include the protocol, hostname, port
(if different than the defaults for `http` and `https`, 80 and 443 respectively), and the
<<server-basePath, `server.basePath`>> (if configured). This setting cannot end in a slash (`/`).
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,25 @@ function setDocsourcePayload(id: string | null, providedPayload: any) {
object = defaults(providedPayload || {}, stubbedSavedObjectIndexPattern(id));
}

const savedObject = {
id: 'id',
version: 'version',
attributes: {
title: 'kibana-*',
timeFieldName: '@timestamp',
fields: '[]',
sourceFilters: '[{"value":"item1"},{"value":"item2"}]',
fieldFormatMap: '{"field":{}}',
typeMeta: '{}',
type: '',
runtimeFieldMap:
'{"aRuntimeField": { "type": "keyword", "script": {"source": "emit(\'hello\')"}}}',
fieldAttrs: '{"aRuntimeField": { "count": 5, "customLabel": "A Runtime Field"}}',
},
type: 'index-pattern',
references: [],
};

describe('IndexPatterns', () => {
let indexPatterns: IndexPatternsService;
let savedObjectsClient: SavedObjectsClientCommon;
Expand Down Expand Up @@ -219,23 +238,14 @@ describe('IndexPatterns', () => {
});

test('savedObjectToSpec', () => {
const savedObject = {
id: 'id',
version: 'version',
attributes: {
title: 'kibana-*',
timeFieldName: '@timestamp',
fields: '[]',
sourceFilters: '[{"value":"item1"},{"value":"item2"}]',
fieldFormatMap: '{"field":{}}',
typeMeta: '{}',
type: '',
},
type: 'index-pattern',
references: [],
};
const spec = indexPatterns.savedObjectToSpec(savedObject);
expect(spec).toMatchSnapshot();
});

expect(indexPatterns.savedObjectToSpec(savedObject)).toMatchSnapshot();
test('correctly composes runtime field', async () => {
setDocsourcePayload('id', savedObject);
const indexPattern = await indexPatterns.get('id');
expect(indexPattern.fields).toMatchSnapshot();
});

test('failed requests are not cached', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -425,8 +425,9 @@ export class IndexPatternsService {
runtimeField: value,
aggregatable: true,
searchable: true,
count: 0,
readFromDocValues: false,
customLabel: spec.fieldAttrs?.[key]?.customLabel,
count: spec.fieldAttrs?.[key]?.count,
};
}
}
Expand Down
3 changes: 2 additions & 1 deletion test/api_integration/apis/telemetry/telemetry_local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,8 @@ export default function ({ getService }: FtrProviderContext) {
});
});

it("should only use the first 10k docs for the application_usage data (they'll be rolled up in a later process)", async () => {
// flaky https://github.com/elastic/kibana/issues/94513
it.skip("should only use the first 10k docs for the application_usage data (they'll be rolled up in a later process)", async () => {
const { body } = await supertest
.post('/api/telemetry/v2/clusters/_stats')
.set('kbn-xsrf', 'xxx')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,9 @@ describe('When invoking Trusted Apps Schema', () => {
const bodyMsg = createNewTrustedApp({
entries: [createConditionEntry(), createConditionEntry()],
});
expect(() => body.validate(bodyMsg)).toThrow('[Path] field can only be used once');
expect(() => body.validate(bodyMsg)).toThrow(
'[entries]: duplicatedEntry.process.executable.caseless'
);
});

it('should validate that `entry.field` hash field value can only be used once', () => {
Expand All @@ -266,7 +268,7 @@ describe('When invoking Trusted Apps Schema', () => {
}),
],
});
expect(() => body.validate(bodyMsg)).toThrow('[Hash] field can only be used once');
expect(() => body.validate(bodyMsg)).toThrow('[entries]: duplicatedEntry.process.hash.*');
});

it('should validate that `entry.field` signer field value can only be used once', () => {
Expand All @@ -282,7 +284,9 @@ describe('When invoking Trusted Apps Schema', () => {
}),
],
});
expect(() => body.validate(bodyMsg)).toThrow('[Signer] field can only be used once');
expect(() => body.validate(bodyMsg)).toThrow(
'[entries]: duplicatedEntry.process.Ext.code_signature'
);
});

it('should validate Hash field valid value', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,10 @@
* 2.0.
*/

import { schema, Type } from '@kbn/config-schema';
import { ConditionEntry, ConditionEntryField, OperatingSystem } from '../types';
import { schema } from '@kbn/config-schema';
import { ConditionEntryField, OperatingSystem } from '../types';
import { getDuplicateFields, isValidHash } from '../validation/trusted_apps';

const entryFieldLabels: { [k in ConditionEntryField]: string } = {
[ConditionEntryField.HASH]: 'Hash',
[ConditionEntryField.PATH]: 'Path',
[ConditionEntryField.SIGNER]: 'Signer',
};

export const DeleteTrustedAppsRequestSchema = {
params: schema.object({
id: schema.string(),
Expand All @@ -30,56 +24,99 @@ export const GetTrustedAppsRequestSchema = {

const ConditionEntryTypeSchema = schema.literal('match');
const ConditionEntryOperatorSchema = schema.literal('included');
const HashConditionEntrySchema = schema.object({
field: schema.literal(ConditionEntryField.HASH),

/*
* A generic Entry schema to be used for a specific entry schema depending on the OS
*/
const CommonEntrySchema = {
field: schema.oneOf([
schema.literal(ConditionEntryField.HASH),
schema.literal(ConditionEntryField.PATH),
]),
type: ConditionEntryTypeSchema,
operator: ConditionEntryOperatorSchema,
value: schema.string({
validate: (hash) => (isValidHash(hash) ? undefined : `Invalid hash value [${hash}]`),
}),
// If field === HASH then validate hash with custom method, else validate string with minLength = 1
value: schema.conditional(
schema.siblingRef('field'),
ConditionEntryField.HASH,
schema.string({
validate: (hash) =>
isValidHash(hash) ? undefined : `invalidField.${ConditionEntryField.HASH}`,
}),
schema.conditional(
schema.siblingRef('field'),
ConditionEntryField.PATH,
schema.string({
validate: (field) =>
field.length > 0 ? undefined : `invalidField.${ConditionEntryField.PATH}`,
}),
schema.string({
validate: (field) =>
field.length > 0 ? undefined : `invalidField.${ConditionEntryField.SIGNER}`,
})
)
),
};

const WindowsEntrySchema = schema.object({
...CommonEntrySchema,
field: schema.oneOf([
schema.literal(ConditionEntryField.HASH),
schema.literal(ConditionEntryField.PATH),
schema.literal(ConditionEntryField.SIGNER),
]),
});
const PathConditionEntrySchema = schema.object({
field: schema.literal(ConditionEntryField.PATH),
type: ConditionEntryTypeSchema,
operator: ConditionEntryOperatorSchema,
value: schema.string({ minLength: 1 }),

const LinuxEntrySchema = schema.object({
...CommonEntrySchema,
});
const SignerConditionEntrySchema = schema.object({
field: schema.literal(ConditionEntryField.SIGNER),
type: ConditionEntryTypeSchema,
operator: ConditionEntryOperatorSchema,
value: schema.string({ minLength: 1 }),

const MacEntrySchema = schema.object({
...CommonEntrySchema,
});

const createNewTrustedAppForOsScheme = <O extends OperatingSystem, E extends ConditionEntry>(
osSchema: Type<O>,
entriesSchema: Type<E>
) =>
schema.object({
name: schema.string({ minLength: 1, maxLength: 256 }),
description: schema.maybe(schema.string({ minLength: 0, maxLength: 256, defaultValue: '' })),
os: osSchema,
entries: schema.arrayOf(entriesSchema, {
minSize: 1,
validate(entries) {
return (
getDuplicateFields(entries)
.map((field) => `[${entryFieldLabels[field]}] field can only be used once`)
.join(', ') || undefined
);
},
}),
});
/*
* Entry Schema depending on Os type using schema.conditional.
* If OS === WINDOWS then use Windows schema,
* else if OS === LINUX then use Linux schema,
* else use Mac schema
*/
const EntrySchemaDependingOnOS = schema.conditional(
schema.siblingRef('os'),
OperatingSystem.WINDOWS,
WindowsEntrySchema,
schema.conditional(
schema.siblingRef('os'),
OperatingSystem.LINUX,
LinuxEntrySchema,
MacEntrySchema
)
);

/*
* Entities array schema.
* The validate function checks there is no duplicated entry inside the array
*/
const EntriesSchema = schema.arrayOf(EntrySchemaDependingOnOS, {
minSize: 1,
validate(entries) {
return (
getDuplicateFields(entries)
.map((field) => `duplicatedEntry.${field}`)
.join(', ') || undefined
);
},
});

export const PostTrustedAppCreateRequestSchema = {
body: schema.oneOf([
createNewTrustedAppForOsScheme(
schema.oneOf([schema.literal(OperatingSystem.LINUX), schema.literal(OperatingSystem.MAC)]),
schema.oneOf([HashConditionEntrySchema, PathConditionEntrySchema])
),
createNewTrustedAppForOsScheme(
body: schema.object({
name: schema.string({ minLength: 1, maxLength: 256 }),
description: schema.maybe(schema.string({ minLength: 0, maxLength: 256, defaultValue: '' })),
os: schema.oneOf([
schema.literal(OperatingSystem.WINDOWS),
schema.oneOf([HashConditionEntrySchema, PathConditionEntrySchema, SignerConditionEntrySchema])
),
]),
schema.literal(OperatingSystem.LINUX),
schema.literal(OperatingSystem.MAC),
]),
entries: EntriesSchema,
}),
};
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,13 @@ export interface GetTrustedListAppsResponse {
data: TrustedApp[];
}

/** API Request body for creating a new Trusted App entry */
export type PostTrustedAppCreateRequest = TypeOf<typeof PostTrustedAppCreateRequestSchema.body>;
/*
* API Request body for creating a new Trusted App entry
* As this is an inferred type and the schema type doesn't match at all with the
* NewTrustedApp type it needs and overwrite from the MacosLinux/Windows custom types
*/
export type PostTrustedAppCreateRequest = TypeOf<typeof PostTrustedAppCreateRequestSchema.body> &
(MacosLinuxConditionEntries | WindowsConditionEntries);

export interface PostTrustedAppCreateResponse {
data: TrustedApp;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
EuiText,
EuiTitle,
} from '@elastic/eui';
import React, { memo, useCallback, useEffect } from 'react';
import React, { memo, useCallback, useEffect, useMemo } from 'react';
import { EuiFlyoutProps } from '@elastic/eui/src/components/flyout/flyout';
import { FormattedMessage } from '@kbn/i18n/react';
import { useDispatch } from 'react-redux';
Expand All @@ -31,7 +31,7 @@ import {
} from '../../store/selectors';
import { AppAction } from '../../../../../common/store/actions';
import { useTrustedAppsSelector } from '../hooks';
import { ABOUT_TRUSTED_APPS } from '../translations';
import { ABOUT_TRUSTED_APPS, CREATE_TRUSTED_APP_ERROR } from '../translations';

type CreateTrustedAppFlyoutProps = Omit<EuiFlyoutProps, 'hideCloseButton'>;
export const CreateTrustedAppFlyout = memo<CreateTrustedAppFlyoutProps>(
Expand All @@ -45,6 +45,15 @@ export const CreateTrustedAppFlyout = memo<CreateTrustedAppFlyoutProps>(

const dataTestSubj = flyoutProps['data-test-subj'];

const creationErrorsMessage = useMemo<string | undefined>(
() =>
creationErrors
? CREATE_TRUSTED_APP_ERROR[creationErrors.message.replace(/(\[(.*)\]\: )/, '')] ||
creationErrors.message
: undefined,
[creationErrors]
);

const getTestId = useCallback(
(suffix: string): string | undefined => {
if (dataTestSubj) {
Expand Down Expand Up @@ -102,7 +111,7 @@ export const CreateTrustedAppFlyout = memo<CreateTrustedAppFlyoutProps>(
fullWidth
onChange={handleFormOnChange}
isInvalid={!!creationErrors}
error={creationErrors?.message}
error={creationErrorsMessage}
data-test-subj={getTestId('createForm')}
/>
</EuiFlyoutBody>
Expand Down
Loading

0 comments on commit 5532d28

Please sign in to comment.