diff --git a/x-pack/plugins/cases/common/api/connectors/mappings.ts b/x-pack/plugins/cases/common/api/connectors/mappings.ts index 33d7441c0c71f..888c7d5f40385 100644 --- a/x-pack/plugins/cases/common/api/connectors/mappings.ts +++ b/x-pack/plugins/cases/common/api/connectors/mappings.ts @@ -7,48 +7,48 @@ import * as rt from 'io-ts'; -const ActionTypeRT = rt.union([ +const ActionTypeRt = rt.union([ rt.literal('append'), rt.literal('nothing'), rt.literal('overwrite'), ]); -const CaseFieldRT = rt.union([ +const CaseFieldRt = rt.union([ rt.literal('title'), rt.literal('description'), rt.literal('comments'), rt.literal('tags'), ]); -const ThirdPartyFieldRT = rt.union([rt.string, rt.literal('not_mapped')]); -export type ActionType = rt.TypeOf; -export type CaseField = rt.TypeOf; -export type ThirdPartyField = rt.TypeOf; +const ThirdPartyFieldRt = rt.union([rt.string, rt.literal('not_mapped')]); +export type ActionType = rt.TypeOf; +export type CaseField = rt.TypeOf; +export type ThirdPartyField = rt.TypeOf; -export const ConnectorMappingsAttributesRT = rt.type({ - action_type: ActionTypeRT, - source: CaseFieldRT, - target: ThirdPartyFieldRT, +const ConnectorMappingsAttributesRt = rt.type({ + action_type: ActionTypeRt, + source: CaseFieldRt, + target: ThirdPartyFieldRt, }); export const ConnectorMappingsRt = rt.type({ - mappings: rt.array(ConnectorMappingsAttributesRT), + mappings: rt.array(ConnectorMappingsAttributesRt), owner: rt.string, }); -export type ConnectorMappingsAttributes = rt.TypeOf; +export type ConnectorMappingsAttributes = rt.TypeOf; export type ConnectorMappings = rt.TypeOf; -const FieldTypeRT = rt.union([rt.literal('text'), rt.literal('textarea')]); +const FieldTypeRt = rt.union([rt.literal('text'), rt.literal('textarea')]); const ConnectorFieldRt = rt.type({ id: rt.string, name: rt.string, required: rt.boolean, - type: FieldTypeRT, + type: FieldTypeRt, }); export type ConnectorField = rt.TypeOf; -const GetDefaultMappingsResponseRt = rt.array(ConnectorMappingsAttributesRT); +const GetDefaultMappingsResponseRt = rt.array(ConnectorMappingsAttributesRt); export type GetDefaultMappingsResponse = rt.TypeOf; diff --git a/x-pack/plugins/cases/server/common/types/connector_mappings.test.ts b/x-pack/plugins/cases/server/common/types/connector_mappings.test.ts new file mode 100644 index 0000000000000..3af1bfe26f00a --- /dev/null +++ b/x-pack/plugins/cases/server/common/types/connector_mappings.test.ts @@ -0,0 +1,24 @@ +/* + * 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 { decodeOrThrow } from '../../../common/api/runtime_types'; +import { ConnectorMappingsPartialRt } from './connector_mappings'; + +describe('mappings', () => { + describe('ConnectorMappingsPartialRt', () => { + it('strips excess fields from the object', () => { + const res = decodeOrThrow(ConnectorMappingsPartialRt)({ bananas: 'yes', owner: 'hi' }); + expect(res).toMatchObject({ + owner: 'hi', + }); + }); + + it('does not throw when the object is empty', () => { + expect(() => decodeOrThrow(ConnectorMappingsPartialRt)({})).not.toThrow(); + }); + }); +}); diff --git a/x-pack/plugins/cases/server/common/types/connector_mappings.ts b/x-pack/plugins/cases/server/common/types/connector_mappings.ts index d4f20075745f2..88792c779ba93 100644 --- a/x-pack/plugins/cases/server/common/types/connector_mappings.ts +++ b/x-pack/plugins/cases/server/common/types/connector_mappings.ts @@ -5,6 +5,12 @@ * 2.0. */ +import * as rt from 'io-ts'; + +import type { SavedObject } from '@kbn/core/server'; +import type { ConnectorMappings } from '../../../common/api'; +import { ConnectorMappingsRt } from '../../../common/api'; + export interface ConnectorMappingsPersistedAttributes { mappings: Array<{ action_type: string; @@ -13,3 +19,10 @@ export interface ConnectorMappingsPersistedAttributes { }>; owner: string; } + +export type ConnectorMappingsTransformed = ConnectorMappings; +export type ConnectorMappingsSavedObjectTransformed = SavedObject; + +export const ConnectorMappingsPartialRt = rt.exact(rt.partial(ConnectorMappingsRt.props)); + +export const ConnectorMappingsTransformedRt = ConnectorMappingsRt; diff --git a/x-pack/plugins/cases/server/services/connector_mappings/index.ts b/x-pack/plugins/cases/server/services/connector_mappings/index.ts index f5301337770d6..570f024e3383b 100644 --- a/x-pack/plugins/cases/server/services/connector_mappings/index.ts +++ b/x-pack/plugins/cases/server/services/connector_mappings/index.ts @@ -7,8 +7,8 @@ import type { Logger, - SavedObject, SavedObjectsFindResponse, + SavedObjectsFindResult, SavedObjectsUpdateResponse, } from '@kbn/core/server'; @@ -18,8 +18,16 @@ import type { PostConnectorMappingsArgs, UpdateConnectorMappingsArgs, } from './types'; -import type { ConnectorMappingsPersistedAttributes } from '../../common/types/connector_mappings'; -import type { ConnectorMappings } from '../../../common/api'; +import type { + ConnectorMappingsPersistedAttributes, + ConnectorMappingsSavedObjectTransformed, + ConnectorMappingsTransformed, +} from '../../common/types/connector_mappings'; +import { + ConnectorMappingsTransformedRt, + ConnectorMappingsPartialRt, +} from '../../common/types/connector_mappings'; +import { decodeOrThrow } from '../../../common/api'; export class ConnectorMappingsService { constructor(private readonly log: Logger) {} @@ -27,7 +35,7 @@ export class ConnectorMappingsService { public async find({ unsecuredSavedObjectsClient, options, - }: FindConnectorMappingsArgs): Promise> { + }: FindConnectorMappingsArgs): Promise> { try { this.log.debug(`Attempting to find all connector mappings`); const connectorMappings = @@ -36,7 +44,15 @@ export class ConnectorMappingsService { type: CASE_CONNECTOR_MAPPINGS_SAVED_OBJECT, }); - return connectorMappings as SavedObjectsFindResponse; + const validatedMappings: Array> = []; + + for (const mapping of connectorMappings.saved_objects) { + const validatedMapping = decodeOrThrow(ConnectorMappingsTransformedRt)(mapping.attributes); + + validatedMappings.push(Object.assign(mapping, { attributes: validatedMapping })); + } + + return Object.assign(connectorMappings, { saved_objects: validatedMappings }); } catch (error) { this.log.error(`Attempting to find all connector mappings: ${error}`); throw error; @@ -48,7 +64,7 @@ export class ConnectorMappingsService { attributes, references, refresh, - }: PostConnectorMappingsArgs): Promise> { + }: PostConnectorMappingsArgs): Promise { try { this.log.debug(`Attempting to POST a new connector mappings`); const connectorMappings = @@ -61,7 +77,11 @@ export class ConnectorMappingsService { } ); - return connectorMappings as SavedObject; + const validatedAttributes = decodeOrThrow(ConnectorMappingsTransformedRt)( + connectorMappings.attributes + ); + + return Object.assign(connectorMappings, { attributes: validatedAttributes }); } catch (error) { this.log.error(`Error on POST a new connector mappings: ${error}`); throw error; @@ -74,7 +94,9 @@ export class ConnectorMappingsService { attributes, references, refresh, - }: UpdateConnectorMappingsArgs): Promise> { + }: UpdateConnectorMappingsArgs): Promise< + SavedObjectsUpdateResponse + > { try { this.log.debug(`Attempting to UPDATE connector mappings ${mappingId}`); const updatedMappings = @@ -88,7 +110,11 @@ export class ConnectorMappingsService { } ); - return updatedMappings as SavedObjectsUpdateResponse; + const validatedAttributes = decodeOrThrow(ConnectorMappingsPartialRt)( + updatedMappings.attributes + ); + + return Object.assign(updatedMappings, { attributes: validatedAttributes }); } catch (error) { this.log.error(`Error on UPDATE connector mappings ${mappingId}: ${error}`); throw error;