mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
* WIP: Use schema.conditional instead of schema.oneOf to ensure the right schema validation from an specific field type * Adds some comments on new schema definition * Use validate functions to set custom messages * Fixes type issue after schema changes. An overwrite of the schema inferred type is needed to match with the NewTrustedApp custom type * Updates schema test after schema changes * Changes error key by type. Updates related unit test * WIP: Parse BE message into an user friendly one. Waiting for final texts * Updates text messages for create trusted app errors Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
0eaaade078
commit
f101d67639
5 changed files with 149 additions and 61 deletions
|
@ -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', () => {
|
||||
|
@ -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', () => {
|
||||
|
@ -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', () => {
|
||||
|
|
|
@ -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(),
|
||||
|
@ -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}]`),
|
||||
}),
|
||||
});
|
||||
const PathConditionEntrySchema = schema.object({
|
||||
field: schema.literal(ConditionEntryField.PATH),
|
||||
type: ConditionEntryTypeSchema,
|
||||
operator: ConditionEntryOperatorSchema,
|
||||
value: schema.string({ minLength: 1 }),
|
||||
});
|
||||
const SignerConditionEntrySchema = schema.object({
|
||||
field: schema.literal(ConditionEntryField.SIGNER),
|
||||
type: ConditionEntryTypeSchema,
|
||||
operator: ConditionEntryOperatorSchema,
|
||||
value: schema.string({ minLength: 1 }),
|
||||
// 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 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
|
||||
);
|
||||
},
|
||||
}),
|
||||
});
|
||||
const LinuxEntrySchema = schema.object({
|
||||
...CommonEntrySchema,
|
||||
});
|
||||
|
||||
const MacEntrySchema = schema.object({
|
||||
...CommonEntrySchema,
|
||||
});
|
||||
|
||||
/*
|
||||
* 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,
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
|
@ -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>(
|
||||
|
@ -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) {
|
||||
|
@ -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>
|
||||
|
|
|
@ -137,3 +137,36 @@ export const LIST_VIEW_TOGGLE_LABEL = i18n.translate(
|
|||
export const NO_RESULTS_MESSAGE = i18n.translate('xpack.securitySolution.trustedapps.noResults', {
|
||||
defaultMessage: 'No items found',
|
||||
});
|
||||
|
||||
export const CREATE_TRUSTED_APP_ERROR: { [K in string]: string } = {
|
||||
[`duplicatedEntry.${ConditionEntryField.HASH}`]: i18n.translate(
|
||||
'xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field.error.duplicated.hash',
|
||||
{ defaultMessage: 'Hash value can only be used once. Please enter a single valid hash.' }
|
||||
),
|
||||
[`duplicatedEntry.${ConditionEntryField.PATH}`]: i18n.translate(
|
||||
'xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field.error.duplicated.path',
|
||||
{ defaultMessage: 'Path value can only be used once. Please enter a single valid path.' }
|
||||
),
|
||||
[`duplicatedEntry.${ConditionEntryField.SIGNER}`]: i18n.translate(
|
||||
'xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field.error.duplicated.signature',
|
||||
{
|
||||
defaultMessage:
|
||||
'Signature value can only be used once. Please enter a single valid signature.',
|
||||
}
|
||||
),
|
||||
[`invalidField.${ConditionEntryField.HASH}`]: i18n.translate(
|
||||
'xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field.error.invalid.hash',
|
||||
{
|
||||
defaultMessage:
|
||||
'An invalid Hash was entered. Please enter in a valid Hash (md5, sha1, or sha256).',
|
||||
}
|
||||
),
|
||||
[`invalidField.${ConditionEntryField.PATH}`]: i18n.translate(
|
||||
'xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field.error.invalid.path',
|
||||
{ defaultMessage: 'An invalid Path was entered. Please enter in a valid Path.' }
|
||||
),
|
||||
[`invalidField.${ConditionEntryField.SIGNER}`]: i18n.translate(
|
||||
'xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field.error.invalid.signature',
|
||||
{ defaultMessage: 'An invalid Signature was entered. Please enter in a valid Signature.' }
|
||||
),
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue