From 640c0b2efbac00a0b849590cbed69ae12f768592 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Fri, 14 Aug 2020 16:01:31 +0100 Subject: [PATCH] revert change in ESO and prohibit partial updates in alert and action clients --- .../actions/server/actions_client.test.ts | 22 +- .../plugins/actions/server/actions_client.ts | 11 +- .../actions/server/create_execute_function.ts | 10 +- .../actions/server/saved_objects/index.ts | 2 + .../saved_objects_client_without_updates.ts | 21 ++ x-pack/plugins/alerts/server/alerts_client.ts | 120 +++++-- .../alerts/server/saved_objects/index.ts | 2 +- ..._objects_client_without_partial_updates.ts | 42 --- .../saved_objects_client_without_updates.ts | 21 ++ ...ypted_saved_objects_client_wrapper.test.ts | 328 ++++++++---------- .../encrypted_saved_objects_client_wrapper.ts | 110 +----- .../api_consumer_plugin/server/index.ts | 29 -- .../tests/encrypted_saved_objects_api.ts | 68 +--- 13 files changed, 330 insertions(+), 456 deletions(-) create mode 100644 x-pack/plugins/actions/server/saved_objects/saved_objects_client_without_updates.ts delete mode 100644 x-pack/plugins/alerts/server/saved_objects/saved_objects_client_without_partial_updates.ts create mode 100644 x-pack/plugins/alerts/server/saved_objects/saved_objects_client_without_updates.ts diff --git a/x-pack/plugins/actions/server/actions_client.test.ts b/x-pack/plugins/actions/server/actions_client.test.ts index 24b41bc197fc1e..c8235157e8c246 100644 --- a/x-pack/plugins/actions/server/actions_client.test.ts +++ b/x-pack/plugins/actions/server/actions_client.test.ts @@ -893,7 +893,7 @@ describe('update()', () => { }, references: [], }); - unsecuredSavedObjectsClient.update.mockResolvedValueOnce({ + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ id: 'my-action', type: 'action', attributes: { @@ -946,7 +946,7 @@ describe('update()', () => { }, references: [], }); - unsecuredSavedObjectsClient.update.mockResolvedValueOnce({ + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ id: 'my-action', type: 'action', attributes: { @@ -972,11 +972,10 @@ describe('update()', () => { name: 'my name', config: {}, }); - expect(unsecuredSavedObjectsClient.update).toHaveBeenCalledTimes(1); - expect(unsecuredSavedObjectsClient.update.mock.calls[0]).toMatchInlineSnapshot(` + expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledTimes(1); + expect(unsecuredSavedObjectsClient.create.mock.calls[0]).toMatchInlineSnapshot(` Array [ "action", - "my-action", Object { "actionTypeId": "my-action-type", "config": Object {}, @@ -984,6 +983,8 @@ describe('update()', () => { "secrets": Object {}, }, Object { + "id": "my-action", + "overwrite": true, "references": Array [], }, ] @@ -1046,7 +1047,7 @@ describe('update()', () => { }, references: [], }); - unsecuredSavedObjectsClient.update.mockResolvedValueOnce({ + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ id: 'my-action', type: 'action', attributes: { @@ -1084,11 +1085,10 @@ describe('update()', () => { c: true, }, }); - expect(unsecuredSavedObjectsClient.update).toHaveBeenCalledTimes(1); - expect(unsecuredSavedObjectsClient.update.mock.calls[0]).toMatchInlineSnapshot(` + expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledTimes(1); + expect(unsecuredSavedObjectsClient.create.mock.calls[0]).toMatchInlineSnapshot(` Array [ "action", - "my-action", Object { "actionTypeId": "my-action-type", "config": Object { @@ -1100,6 +1100,8 @@ describe('update()', () => { "secrets": Object {}, }, Object { + "id": "my-action", + "overwrite": true, "references": Array [], }, ] @@ -1124,7 +1126,7 @@ describe('update()', () => { }, references: [], }); - unsecuredSavedObjectsClient.update.mockResolvedValueOnce({ + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ id: 'my-action', type: 'action', attributes: { diff --git a/x-pack/plugins/actions/server/actions_client.ts b/x-pack/plugins/actions/server/actions_client.ts index b62ca500edccaf..79f14002be0c00 100644 --- a/x-pack/plugins/actions/server/actions_client.ts +++ b/x-pack/plugins/actions/server/actions_client.ts @@ -6,7 +6,6 @@ import Boom from 'boom'; import { ILegacyScopedClusterClient, - SavedObjectsClientContract, SavedObjectAttributes, SavedObject, KibanaRequest, @@ -31,6 +30,7 @@ import { } from './create_execute_function'; import { ActionsAuthorization } from './authorization/actions_authorization'; import { ActionType } from '../common'; +import { SavedObjectsClientWithoutUpdates } from './saved_objects'; // We are assuming there won't be many actions. This is why we will load // all the actions in advance and assume the total count to not go over 10000. @@ -55,7 +55,7 @@ interface ConstructorOptions { defaultKibanaIndex: string; scopedClusterClient: ILegacyScopedClusterClient; actionTypeRegistry: ActionTypeRegistry; - unsecuredSavedObjectsClient: SavedObjectsClientContract; + unsecuredSavedObjectsClient: SavedObjectsClientWithoutUpdates; preconfiguredActions: PreConfiguredAction[]; actionExecutor: ActionExecutorContract; executionEnqueuer: ExecutionEnqueuer; @@ -71,7 +71,7 @@ interface UpdateOptions { export class ActionsClient { private readonly defaultKibanaIndex: string; private readonly scopedClusterClient: ILegacyScopedClusterClient; - private readonly unsecuredSavedObjectsClient: SavedObjectsClientContract; + private readonly unsecuredSavedObjectsClient: SavedObjectsClientWithoutUpdates; private readonly actionTypeRegistry: ActionTypeRegistry; private readonly preconfiguredActions: PreConfiguredAction[]; private readonly actionExecutor: ActionExecutorContract; @@ -162,9 +162,8 @@ export class ActionsClient { this.actionTypeRegistry.ensureActionTypeEnabled(actionTypeId); - const result = await this.unsecuredSavedObjectsClient.update( + const result = await this.unsecuredSavedObjectsClient.create( 'action', - id, { ...attributes, actionTypeId, @@ -174,6 +173,8 @@ export class ActionsClient { }, omitBy( { + id, + overwrite: true, references, version, }, diff --git a/x-pack/plugins/actions/server/create_execute_function.ts b/x-pack/plugins/actions/server/create_execute_function.ts index 85052eef93e051..048d5a401769d8 100644 --- a/x-pack/plugins/actions/server/create_execute_function.ts +++ b/x-pack/plugins/actions/server/create_execute_function.ts @@ -4,10 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsClientContract } from '../../../../src/core/server'; import { TaskManagerStartContract } from '../../task_manager/server'; import { RawAction, ActionTypeRegistryContract, PreConfiguredAction } from './types'; -import { ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE } from './saved_objects'; +import { + ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE, + SavedObjectsClientWithoutUpdates, +} from './saved_objects'; interface CreateExecuteFunctionOptions { taskManager: TaskManagerStartContract; @@ -24,7 +26,7 @@ export interface ExecuteOptions { } export type ExecutionEnqueuer = ( - savedObjectsClient: SavedObjectsClientContract, + savedObjectsClient: SavedObjectsClientWithoutUpdates, options: ExecuteOptions ) => Promise; @@ -35,7 +37,7 @@ export function createExecutionEnqueuerFunction({ preconfiguredActions, }: CreateExecuteFunctionOptions) { return async function execute( - savedObjectsClient: SavedObjectsClientContract, + savedObjectsClient: SavedObjectsClientWithoutUpdates, { id, params, spaceId, apiKey }: ExecuteOptions ) { if (isESOUsingEphemeralEncryptionKey === true) { diff --git a/x-pack/plugins/actions/server/saved_objects/index.ts b/x-pack/plugins/actions/server/saved_objects/index.ts index 54f186acc1ba57..312a7264d986cd 100644 --- a/x-pack/plugins/actions/server/saved_objects/index.ts +++ b/x-pack/plugins/actions/server/saved_objects/index.ts @@ -8,6 +8,8 @@ import { SavedObjectsServiceSetup } from 'kibana/server'; import mappings from './mappings.json'; import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objects/server'; +export { SavedObjectsClientWithoutUpdates } from './saved_objects_client_without_updates'; + export const ACTION_SAVED_OBJECT_TYPE = 'action'; export const ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE = 'action_task_params'; diff --git a/x-pack/plugins/actions/server/saved_objects/saved_objects_client_without_updates.ts b/x-pack/plugins/actions/server/saved_objects/saved_objects_client_without_updates.ts new file mode 100644 index 00000000000000..10ac773cb1f77a --- /dev/null +++ b/x-pack/plugins/actions/server/saved_objects/saved_objects_client_without_updates.ts @@ -0,0 +1,21 @@ +/* + * 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 { SavedObjectsClientContract, SavedObjectsCreateOptions, SavedObject } from 'kibana/server'; + +type AlertSavedObjectsCreateOptions = Omit; +type AlertSavedObjectsUpdateOptions = Omit & + Pick, 'id' | 'overwrite'>; + +export type SavedObjectsClientWithoutUpdates = Omit< + SavedObjectsClientContract, + 'create' | 'update' | 'bulkUpdate' +> & { + create( + type: string, + attributes: T, + options?: AlertSavedObjectsCreateOptions | AlertSavedObjectsUpdateOptions + ): Promise>; +}; diff --git a/x-pack/plugins/alerts/server/alerts_client.ts b/x-pack/plugins/alerts/server/alerts_client.ts index 0c0c2eaa990070..7734d089322595 100644 --- a/x-pack/plugins/alerts/server/alerts_client.ts +++ b/x-pack/plugins/alerts/server/alerts_client.ts @@ -37,7 +37,7 @@ import { WriteOperations, ReadOperations, } from './authorization/alerts_authorization'; -import { SavedObjectsClientWithoutPartialUpdates } from './saved_objects'; +import { SavedObjectsClientWithoutUpdates } from './saved_objects'; export interface RegistryAlertTypeWithAuth extends RegistryAlertType { authorizedConsumers: string[]; @@ -53,7 +53,7 @@ export type InvalidateAPIKeyResult = export interface ConstructorOptions { logger: Logger; taskManager: TaskManagerStartContract; - unsecuredSavedObjectsClient: SavedObjectsClientWithoutPartialUpdates; + unsecuredSavedObjectsClient: SavedObjectsClientWithoutUpdates; authorization: AlertsAuthorization; actionsAuthorization: ActionsAuthorization; alertTypeRegistry: AlertTypeRegistry; @@ -135,7 +135,7 @@ export class AlertsClient { private readonly spaceId?: string; private readonly namespace?: string; private readonly taskManager: TaskManagerStartContract; - private readonly unsecuredSavedObjectsClient: SavedObjectsClientWithoutPartialUpdates; + private readonly unsecuredSavedObjectsClient: SavedObjectsClientWithoutUpdates; private readonly authorization: AlertsAuthorization; private readonly alertTypeRegistry: AlertTypeRegistry; private readonly createAPIKey: (name: string) => Promise; @@ -228,15 +228,16 @@ export class AlertsClient { } throw e; } - await this.unsecuredSavedObjectsClient.update( + await this.unsecuredSavedObjectsClient.create( 'alert', - createdAlert.id, { ...createdAlert.attributes, ...encryptedAttributes, scheduledTaskId: scheduledTask.id, }, { + id: createdAlert.id, + overwrite: true, references, } ); @@ -430,9 +431,8 @@ export class AlertsClient { : null; const apiKeyAttributes = this.apiKeyAsAlertAttributes(createdAPIKey, username); - const updatedObject = await this.unsecuredSavedObjectsClient.update( + const updatedObject = await this.unsecuredSavedObjectsClient.create( 'alert', - id, { ...attributes, ...data, @@ -442,6 +442,8 @@ export class AlertsClient { updatedBy: username, }, { + id, + overwrite: true, version, references, } @@ -506,9 +508,8 @@ export class AlertsClient { } const username = await this.getUserName(); - await this.unsecuredSavedObjectsClient.update( + await this.unsecuredSavedObjectsClient.create( 'alert', - id, { ...attributes, ...this.apiKeyAsAlertAttributes( @@ -517,7 +518,15 @@ export class AlertsClient { ), updatedBy: username, }, - omitBy({ version, references }, isUndefined) + omitBy( + { + id, + overwrite: true, + version, + references, + }, + isUndefined + ) ); if (apiKeyToInvalidate) { @@ -588,21 +597,35 @@ export class AlertsClient { ), updatedBy: username, }; - const updatedAlert = await this.unsecuredSavedObjectsClient.update( + const updatedAlert = await this.unsecuredSavedObjectsClient.create( 'alert', - id, updatedAttributes, - omitBy({ version, references }, isUndefined) + omitBy( + { + id, + overwrite: true, + version, + references, + }, + isUndefined + ) ); const scheduledTask = await this.scheduleAlert(id, attributes.alertTypeId); - await this.unsecuredSavedObjectsClient.update( + await this.unsecuredSavedObjectsClient.create( 'alert', - id, { ...updatedAttributes, scheduledTaskId: scheduledTask.id, }, - omitBy({ version: updatedAlert.version, references: updatedAlert.references }, isUndefined) + omitBy( + { + id, + overwrite: true, + version: updatedAlert.version, + references: updatedAlert.references, + }, + isUndefined + ) ); if (apiKeyToInvalidate) { await this.invalidateApiKey({ apiKey: apiKeyToInvalidate }); @@ -643,9 +666,8 @@ export class AlertsClient { ); if (attributes.enabled === true) { - await this.unsecuredSavedObjectsClient.update( + await this.unsecuredSavedObjectsClient.create( 'alert', - id, { ...(omit( attributes, @@ -656,7 +678,15 @@ export class AlertsClient { enabled: false, updatedBy: await this.getUserName(), }, - omitBy({ version, references }, isUndefined) + omitBy( + { + id, + overwrite: true, + version, + references, + }, + isUndefined + ) ); await Promise.all([ @@ -686,16 +716,23 @@ export class AlertsClient { await this.actionsAuthorization.ensureAuthorized('execute'); } - await this.unsecuredSavedObjectsClient.update( + await this.unsecuredSavedObjectsClient.create( 'alert', - id, { ...attributes, muteAll: true, mutedInstanceIds: [], updatedBy: await this.getUserName(), }, - omitBy({ version, references }, isUndefined) + omitBy( + { + id, + overwrite: true, + version, + references, + }, + isUndefined + ) ); } @@ -717,16 +754,23 @@ export class AlertsClient { await this.actionsAuthorization.ensureAuthorized('execute'); } - await this.unsecuredSavedObjectsClient.update( + await this.unsecuredSavedObjectsClient.create( 'alert', - id, { ...attributes, muteAll: false, mutedInstanceIds: [], updatedBy: await this.getUserName(), }, - omitBy({ version, references }, isUndefined) + omitBy( + { + id, + overwrite: true, + version, + references, + }, + isUndefined + ) ); } @@ -756,15 +800,22 @@ export class AlertsClient { const mutedInstanceIds = attributes.mutedInstanceIds || []; if (!attributes.muteAll && !mutedInstanceIds.includes(alertInstanceId)) { mutedInstanceIds.push(alertInstanceId); - await this.unsecuredSavedObjectsClient.update( + await this.unsecuredSavedObjectsClient.create( 'alert', - alertId, { ...attributes, mutedInstanceIds, updatedBy: await this.getUserName(), }, - omitBy({ version, references }, isUndefined) + omitBy( + { + id: alertId, + overwrite: true, + version, + references, + }, + isUndefined + ) ); } } @@ -798,15 +849,22 @@ export class AlertsClient { const mutedInstanceIds = attributes.mutedInstanceIds || []; if (!attributes.muteAll && mutedInstanceIds.includes(alertInstanceId)) { - await this.unsecuredSavedObjectsClient.update( + await this.unsecuredSavedObjectsClient.create( 'alert', - alertId, { ...attributes, updatedBy: await this.getUserName(), mutedInstanceIds: mutedInstanceIds.filter((id: string) => id !== alertInstanceId), }, - omitBy({ version, references }, isUndefined) + omitBy( + { + id: alertId, + overwrite: true, + version, + references, + }, + isUndefined + ) ); } } diff --git a/x-pack/plugins/alerts/server/saved_objects/index.ts b/x-pack/plugins/alerts/server/saved_objects/index.ts index 32f14a666cfb63..652b98d6b0c763 100644 --- a/x-pack/plugins/alerts/server/saved_objects/index.ts +++ b/x-pack/plugins/alerts/server/saved_objects/index.ts @@ -8,7 +8,7 @@ import { SavedObjectsServiceSetup } from 'kibana/server'; import mappings from './mappings.json'; import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objects/server'; -export { SavedObjectsClientWithoutPartialUpdates } from './saved_objects_client_without_partial_updates'; +export { SavedObjectsClientWithoutUpdates } from './saved_objects_client_without_updates'; export function setupSavedObjects( savedObjects: SavedObjectsServiceSetup, diff --git a/x-pack/plugins/alerts/server/saved_objects/saved_objects_client_without_partial_updates.ts b/x-pack/plugins/alerts/server/saved_objects/saved_objects_client_without_partial_updates.ts deleted file mode 100644 index 8d603942419d13..00000000000000 --- a/x-pack/plugins/alerts/server/saved_objects/saved_objects_client_without_partial_updates.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 { - SavedObjectsClientContract, - SavedObjectsUpdateOptions, - SavedObjectsUpdateResponse, - SavedObjectsBulkUpdateOptions, - SavedObjectsBulkUpdateObject, - SavedObjectsBulkUpdateResponse, -} from 'kibana/server'; - -export interface SavedObjectsBulkUpdateObjectWithoutPartialUpdates - extends SavedObjectsBulkUpdateObject { - attributes: T; -} - -export interface SavedObjectsBulkUpdateResponseWithoutPartialUpdates - extends SavedObjectsBulkUpdateResponse { - saved_objects: Array>; -} - -export interface SavedObjectsUpdateResponseWithoutPartialUpdates - extends SavedObjectsUpdateResponse { - attributes: T; -} - -export interface SavedObjectsClientWithoutPartialUpdates extends SavedObjectsClientContract { - update( - type: string, - id: string, - attributes: T, - options: SavedObjectsUpdateOptions - ): Promise>; - - bulkUpdate( - objects: Array>, - options?: SavedObjectsBulkUpdateOptions - ): Promise>; -} diff --git a/x-pack/plugins/alerts/server/saved_objects/saved_objects_client_without_updates.ts b/x-pack/plugins/alerts/server/saved_objects/saved_objects_client_without_updates.ts new file mode 100644 index 00000000000000..0c3ecf7e3c78e3 --- /dev/null +++ b/x-pack/plugins/alerts/server/saved_objects/saved_objects_client_without_updates.ts @@ -0,0 +1,21 @@ +/* + * 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 { SavedObjectsClientContract, SavedObjectsCreateOptions } from 'kibana/server'; + +type AlertSavedObjectsCreateOptions = Omit; +type AlertSavedObjectsUpdateOptions = Omit & + Pick, 'id' | 'overwrite'>; + +export type SavedObjectsClientWithoutUpdates = Omit< + SavedObjectsClientContract, + 'create' | 'update' | 'bulkUpdate' +> & { + create( + type: string, + attributes: T, + options?: AlertSavedObjectsCreateOptions | AlertSavedObjectsUpdateOptions + ): Promise>; +}; diff --git a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.test.ts b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.test.ts index 3c0f2f1f8d2c82..b704aaaba03433 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.test.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.test.ts @@ -69,6 +69,58 @@ describe('#create', () => { expect(mockBaseClient.create).not.toHaveBeenCalled(); }); + it('allows a specified ID when overwriting an existing object', async () => { + const attributes = { + attrOne: 'one', + attrSecret: 'secret', + attrNotSoSecret: 'not-so-secret', + attrThree: 'three', + }; + const options = { id: 'predefined-uuid', overwrite: true }; + const mockedResponse = { + id: 'predefined-uuid', + type: 'known-type', + attributes: { + attrOne: 'one', + attrSecret: '*secret*', + attrNotSoSecret: '*not-so-secret*', + attrThree: 'three', + }, + references: [], + }; + + mockBaseClient.create.mockResolvedValue(mockedResponse); + + expect(await wrapper.create('known-type', attributes, options)).toEqual({ + ...mockedResponse, + attributes: { attrOne: 'one', attrNotSoSecret: 'not-so-secret', attrThree: 'three' }, + }); + + expect(encryptedSavedObjectsServiceMockInstance.encryptAttributes).toHaveBeenCalledTimes(1); + expect(encryptedSavedObjectsServiceMockInstance.encryptAttributes).toHaveBeenCalledWith( + { type: 'known-type', id: 'predefined-uuid' }, + { + attrOne: 'one', + attrSecret: 'secret', + attrNotSoSecret: 'not-so-secret', + attrThree: 'three', + }, + { user: mockAuthenticatedUser() } + ); + + expect(mockBaseClient.create).toHaveBeenCalledTimes(1); + expect(mockBaseClient.create).toHaveBeenCalledWith( + 'known-type', + { + attrOne: 'one', + attrSecret: '*secret*', + attrNotSoSecret: '*not-so-secret*', + attrThree: 'three', + }, + { id: 'predefined-uuid', overwrite: true } + ); + }); + it('generates ID, encrypts attributes and strips them from response except for ones with `dangerouslyExposeValue` set to `true`', async () => { const attributes = { attrOne: 'one', @@ -249,6 +301,77 @@ describe('#bulkCreate', () => { expect(mockBaseClient.bulkCreate).not.toHaveBeenCalled(); }); + it('allows a specified ID when overwriting an existing object', async () => { + const attributes = { + attrOne: 'one', + attrSecret: 'secret', + attrNotSoSecret: 'not-so-secret', + attrThree: 'three', + }; + const mockedResponse = { + saved_objects: [ + { + id: 'predefined-uuid', + type: 'known-type', + attributes: { ...attributes, attrSecret: '*secret*', attrNotSoSecret: '*not-so-secret*' }, + references: [], + }, + { + id: 'some-id', + type: 'unknown-type', + attributes, + references: [], + }, + ], + }; + + mockBaseClient.bulkCreate.mockResolvedValue(mockedResponse); + + const bulkCreateParams = [ + { id: 'predefined-uuid', type: 'known-type', attributes }, + { type: 'unknown-type', attributes }, + ]; + + await expect(wrapper.bulkCreate(bulkCreateParams, { overwrite: true })).resolves.toEqual({ + saved_objects: [ + { + ...mockedResponse.saved_objects[0], + attributes: { attrOne: 'one', attrNotSoSecret: 'not-so-secret', attrThree: 'three' }, + }, + mockedResponse.saved_objects[1], + ], + }); + + expect(encryptedSavedObjectsServiceMockInstance.encryptAttributes).toHaveBeenCalledTimes(1); + expect(encryptedSavedObjectsServiceMockInstance.encryptAttributes).toHaveBeenCalledWith( + { type: 'known-type', id: 'predefined-uuid' }, + { + attrOne: 'one', + attrSecret: 'secret', + attrNotSoSecret: 'not-so-secret', + attrThree: 'three', + }, + { user: mockAuthenticatedUser() } + ); + + expect(mockBaseClient.bulkCreate).toHaveBeenCalledTimes(1); + expect(mockBaseClient.bulkCreate).toHaveBeenCalledWith( + [ + { + ...bulkCreateParams[0], + attributes: { + attrOne: 'one', + attrSecret: '*secret*', + attrNotSoSecret: '*not-so-secret*', + attrThree: 'three', + }, + }, + bulkCreateParams[1], + ], + { overwrite: true } + ); + }); + it('generates ID, encrypts attributes and strips them from response except for ones with `dangerouslyExposeValue` set to `true`', async () => { const attributes = { attrOne: 'one', @@ -457,11 +580,11 @@ describe('#bulkUpdate', () => { attrSecret: `*${doc.attributes.attrSecret}*`, attrNotSoSecret: `*${doc.attributes.attrNotSoSecret}*`, }, - references: [], + references: undefined, })), }; - mockBaseClient.bulkCreate.mockResolvedValue(mockedResponse); + mockBaseClient.bulkUpdate.mockResolvedValue(mockedResponse); await expect( wrapper.bulkUpdate( @@ -478,7 +601,6 @@ describe('#bulkUpdate', () => { attrNotSoSecret: 'not-so-secret', attrThree: 'three', }, - references: [], }, { id: 'some-id-2', @@ -488,7 +610,6 @@ describe('#bulkUpdate', () => { attrNotSoSecret: 'not-so-secret 2', attrThree: 'three 2', }, - references: [], }, ], }); @@ -515,8 +636,8 @@ describe('#bulkUpdate', () => { { user: mockAuthenticatedUser() } ); - expect(mockBaseClient.bulkCreate).toHaveBeenCalledTimes(1); - expect(mockBaseClient.bulkCreate).toHaveBeenCalledWith( + expect(mockBaseClient.bulkUpdate).toHaveBeenCalledTimes(1); + expect(mockBaseClient.bulkUpdate).toHaveBeenCalledWith( [ { id: 'some-id', @@ -539,160 +660,6 @@ describe('#bulkUpdate', () => { }, }, ], - { overwrite: true } - ); - }); - - it('handles mixed encrypted and non encrypted objects', async () => { - const encryptedDocs = [ - { - id: 'some-encrypted-id', - type: 'known-type', - attributes: { - attrOne: 'one', - attrSecret: 'secret', - attrNotSoSecret: 'not-so-secret', - attrThree: 'three', - }, - }, - { - id: 'some-encrypted-id-2', - type: 'known-type', - attributes: { - attrOne: 'one 2', - attrSecret: 'secret 2', - attrNotSoSecret: 'not-so-secret 2', - attrThree: 'three 2', - }, - }, - ]; - - mockBaseClient.bulkCreate.mockResolvedValue({ - saved_objects: encryptedDocs.map((doc) => ({ - ...doc, - attributes: { - ...doc.attributes, - attrSecret: `*${doc.attributes.attrSecret}*`, - attrNotSoSecret: `*${doc.attributes.attrNotSoSecret}*`, - }, - references: [], - })), - }); - - const nonEncyptedDoc = { - id: 'some-id', - type: 'unknown-type', - attributes: { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }, - references: [], - }; - mockBaseClient.bulkUpdate.mockResolvedValue({ - saved_objects: [nonEncyptedDoc], - }); - - await expect( - wrapper.bulkUpdate( - [ - { ...encryptedDocs[0] }, - { - type: 'unknown-type', - id: 'some-id', - attributes: nonEncyptedDoc.attributes, - version: 'some-version', - }, - { ...encryptedDocs[1] }, - ], - {} - ) - ).resolves.toEqual({ - saved_objects: [ - { - id: 'some-encrypted-id', - type: 'known-type', - attributes: { - attrOne: 'one', - attrNotSoSecret: 'not-so-secret', - attrThree: 'three', - }, - references: [], - }, - { - id: 'some-id', - type: 'unknown-type', - attributes: nonEncyptedDoc.attributes, - references: [], - }, - { - id: 'some-encrypted-id-2', - type: 'known-type', - attributes: { - attrOne: 'one 2', - attrNotSoSecret: 'not-so-secret 2', - attrThree: 'three 2', - }, - references: [], - }, - ], - }); - - expect(encryptedSavedObjectsServiceMockInstance.encryptAttributes).toHaveBeenCalledTimes(2); - expect(encryptedSavedObjectsServiceMockInstance.encryptAttributes).toHaveBeenCalledWith( - { type: 'known-type', id: 'some-encrypted-id' }, - { - attrOne: 'one', - attrSecret: 'secret', - attrNotSoSecret: 'not-so-secret', - attrThree: 'three', - }, - { user: mockAuthenticatedUser() } - ); - expect(encryptedSavedObjectsServiceMockInstance.encryptAttributes).toHaveBeenCalledWith( - { type: 'known-type', id: 'some-encrypted-id-2' }, - { - attrOne: 'one 2', - attrSecret: 'secret 2', - attrNotSoSecret: 'not-so-secret 2', - attrThree: 'three 2', - }, - { user: mockAuthenticatedUser() } - ); - - expect(mockBaseClient.bulkCreate).toHaveBeenCalledTimes(1); - expect(mockBaseClient.bulkCreate).toHaveBeenCalledWith( - [ - { - id: 'some-encrypted-id', - type: 'known-type', - attributes: { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - }, - { - id: 'some-encrypted-id-2', - type: 'known-type', - attributes: { - attrOne: 'one 2', - attrSecret: '*secret 2*', - attrNotSoSecret: '*not-so-secret 2*', - attrThree: 'three 2', - }, - }, - ], - { overwrite: true } - ); - - expect(mockBaseClient.bulkUpdate).toHaveBeenCalledTimes(1); - expect(mockBaseClient.bulkUpdate).toHaveBeenCalledWith( - [ - { - type: 'unknown-type', - id: 'some-id', - attributes: nonEncyptedDoc.attributes, - version: 'some-version', - }, - ], {} ); }); @@ -713,8 +680,8 @@ describe('#bulkUpdate', () => { ]; const options = { namespace }; - mockBaseClient.bulkCreate.mockResolvedValue({ - saved_objects: docs.map((doc) => ({ ...doc, references: [] })), + mockBaseClient.bulkUpdate.mockResolvedValue({ + saved_objects: docs.map((doc) => ({ ...doc, references: undefined })), }); await expect(wrapper.bulkUpdate(docs, options)).resolves.toEqual({ @@ -727,7 +694,7 @@ describe('#bulkUpdate', () => { attrThree: 'three', }, version: 'some-version', - references: [], + references: undefined, }, ], }); @@ -743,8 +710,8 @@ describe('#bulkUpdate', () => { { user: mockAuthenticatedUser() } ); - expect(mockBaseClient.bulkCreate).toHaveBeenCalledTimes(1); - expect(mockBaseClient.bulkCreate).toHaveBeenCalledWith( + expect(mockBaseClient.bulkUpdate).toHaveBeenCalledTimes(1); + expect(mockBaseClient.bulkUpdate).toHaveBeenCalledWith( [ { id: 'some-id', @@ -759,7 +726,7 @@ describe('#bulkUpdate', () => { references: undefined, }, ], - { ...options, overwrite: true } + options ); }; @@ -1450,7 +1417,7 @@ describe('#update', () => { references: [], }; - mockBaseClient.create.mockResolvedValue(mockedResponse); + mockBaseClient.update.mockResolvedValue(mockedResponse); await expect(wrapper.update('known-type', 'some-id', attributes, options)).resolves.toEqual({ ...mockedResponse, @@ -1469,17 +1436,17 @@ describe('#update', () => { { user: mockAuthenticatedUser() } ); - expect(mockBaseClient.create).toHaveBeenCalledTimes(1); - expect(mockBaseClient.create).toHaveBeenCalledWith( + expect(mockBaseClient.update).toHaveBeenCalledTimes(1); + expect(mockBaseClient.update).toHaveBeenCalledWith( 'known-type', - + 'some-id', { attrOne: 'one', attrSecret: '*secret*', attrNotSoSecret: '*not-so-secret*', attrThree: 'three', }, - { id: 'some-id', overwrite: true, ...options } + options ); }); @@ -1489,7 +1456,7 @@ describe('#update', () => { const options = { version: 'some-version', namespace }; const mockedResponse = { id: 'some-id', type: 'known-type', attributes, references: [] }; - mockBaseClient.create.mockResolvedValue(mockedResponse); + mockBaseClient.update.mockResolvedValue(mockedResponse); await expect(wrapper.update('known-type', 'some-id', attributes, options)).resolves.toEqual({ ...mockedResponse, @@ -1507,12 +1474,12 @@ describe('#update', () => { { user: mockAuthenticatedUser() } ); - expect(mockBaseClient.create).toHaveBeenCalledTimes(1); - expect(mockBaseClient.create).toHaveBeenCalledWith( + expect(mockBaseClient.update).toHaveBeenCalledTimes(1); + expect(mockBaseClient.update).toHaveBeenCalledWith( 'known-type', - + 'some-id', { attrOne: 'one', attrSecret: '*secret*', attrThree: 'three' }, - { id: 'some-id', overwrite: true, ...options } + options ); }; @@ -1528,7 +1495,7 @@ describe('#update', () => { it('fails if base client fails', async () => { const failureReason = new Error('Something bad happened...'); - mockBaseClient.create.mockRejectedValue(failureReason); + mockBaseClient.update.mockRejectedValue(failureReason); await expect( wrapper.update('known-type', 'some-id', { @@ -1538,11 +1505,12 @@ describe('#update', () => { }) ).rejects.toThrowError(failureReason); - expect(mockBaseClient.create).toHaveBeenCalledTimes(1); - expect(mockBaseClient.create).toHaveBeenCalledWith( + expect(mockBaseClient.update).toHaveBeenCalledTimes(1); + expect(mockBaseClient.update).toHaveBeenCalledWith( 'known-type', + 'some-id', { attrOne: 'one', attrSecret: '*secret*', attrThree: 'three' }, - { id: 'some-id', overwrite: true } + undefined ); }); }); diff --git a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.ts b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.ts index 8573d34ea0f21f..ea4b0ea0a96e64 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.ts @@ -60,13 +60,13 @@ export class EncryptedSavedObjectsClientWrapper implements SavedObjectsClientCon // Saved objects with encrypted attributes should have IDs that are hard to guess especially // since IDs are part of the AAD used during encryption, that's why we control them within this // wrapper and don't allow consumers to specify their own IDs directly. - if (options.id) { + if (options.id && !options.overwrite) { throw new Error( 'Predefined IDs are not allowed for saved objects with encrypted attributes.' ); } - const id = generateID(); + const id = options.id ?? generateID(); const namespace = getDescriptorNamespace( this.options.baseTypeRegistry, type, @@ -89,7 +89,7 @@ export class EncryptedSavedObjectsClientWrapper implements SavedObjectsClientCon public async bulkCreate( objects: Array>, - options?: SavedObjectsBaseOptions + options?: SavedObjectsBaseOptions & Pick ) { // We encrypt attributes for every object in parallel and that can potentially exhaust libuv or // NodeJS thread pool. If it turns out to be a problem, we can consider switching to the @@ -103,13 +103,13 @@ export class EncryptedSavedObjectsClientWrapper implements SavedObjectsClientCon // Saved objects with encrypted attributes should have IDs that are hard to guess especially // since IDs are part of the AAD used during encryption, that's why we control them within this // wrapper and don't allow consumers to specify their own IDs directly. - if (object.id) { + if (object.id && !options?.overwrite) { throw new Error( 'Predefined IDs are not allowed for saved objects with encrypted attributes.' ); } - const id = generateID(); + const id = object?.id ?? generateID(); const namespace = getDescriptorNamespace( this.options.baseTypeRegistry, object.type, @@ -137,41 +137,15 @@ export class EncryptedSavedObjectsClientWrapper implements SavedObjectsClientCon objects: Array>, options?: SavedObjectsBaseOptions ) { - const { - nonEncryptedObjects, - nonEncryptedObjectsIndicies, - encryptableObjects, - encryptableObjectsIndicies, - } = objects.reduce<{ - nonEncryptedObjects: Array>; - encryptableObjects: Array>; - nonEncryptedObjectsIndicies: number[]; - encryptableObjectsIndicies: number[]; - }>( - (partition, object, index) => { - if (this.options.service.isRegistered(object.type)) { - partition.encryptableObjects.push(object); - partition.encryptableObjectsIndicies.push(index); - } else { - partition.nonEncryptedObjects.push(object); - partition.nonEncryptedObjectsIndicies.push(index); - } - return partition; - }, - { - nonEncryptedObjects: [], - encryptableObjects: [], - nonEncryptedObjectsIndicies: [], - encryptableObjectsIndicies: [], - } - ); - // We encrypt attributes for every object in parallel and that can potentially exhaust libuv or // NodeJS thread pool. If it turns out to be a problem, we can consider switching to the // sequential processing. const encryptedObjects = await Promise.all( - encryptableObjects.map(async (object) => { + objects.map(async (object) => { const { type, id, attributes } = object; + if (!this.options.service.isRegistered(type)) { + return object; + } const namespace = getDescriptorNamespace( this.options.baseTypeRegistry, type, @@ -189,38 +163,7 @@ export class EncryptedSavedObjectsClientWrapper implements SavedObjectsClientCon ); return await this.handleEncryptedAttributesInBulkResponse( - { - saved_objects: [ - // match up each object in the response with - // the index it had in the original input. - // if the object didn't have an index then assign - // the max possible index so it'll get pushed to the bottom - ...this.zip( - encryptedObjects.length - ? ( - await this.options.baseClient.bulkCreate(encryptedObjects, { - ...options, - overwrite: true, - }) - ).saved_objects - : [], - encryptableObjectsIndicies, - Number.MAX_VALUE - ), - ...this.zip( - nonEncryptedObjects.length - ? (await this.options.baseClient.bulkUpdate(nonEncryptedObjects, options)) - .saved_objects - : [], - nonEncryptedObjectsIndicies, - Number.MAX_VALUE - ), - ] - // sort by the index in the original input - .sort(([, leftIndex], [, rightIndex]) => leftIndex - rightIndex) - // drop the index now that we've sorted - .map(([object]) => object), - }, + await this.options.baseClient.bulkUpdate(encryptedObjects, options), objects ); } @@ -257,7 +200,7 @@ export class EncryptedSavedObjectsClientWrapper implements SavedObjectsClientCon public async update( type: string, id: string, - attributes: T, + attributes: Partial, options?: SavedObjectsUpdateOptions ) { if (!this.options.service.isRegistered(type)) { @@ -269,20 +212,13 @@ export class EncryptedSavedObjectsClientWrapper implements SavedObjectsClientCon options?.namespace ); return this.handleEncryptedAttributesInResponse( - await this.options.baseClient.create( + await this.options.baseClient.update( type, - (await this.options.service.encryptAttributes( - { type, id, namespace }, - attributes as Record, - { - user: this.options.getCurrentUser(), - } - )) as T, - { - ...options, - id, - overwrite: true, - } + id, + await this.options.service.encryptAttributes({ type, id, namespace }, attributes, { + user: this.options.getCurrentUser(), + }), + options ), attributes, namespace @@ -366,16 +302,4 @@ export class EncryptedSavedObjectsClientWrapper implements SavedObjectsClientCon return response; } - - /** - * Zip two arrays together so each cell contains a tupple of two item, one from each array in that index. - * All items from the left array will be retained, and only as many items as needed will be pulled from right - * @param left array of items of any type - * @param right array of items of any type - * @param defaultZipValue default value to use when pulling from the left array and - * a corresponding item is missing in the right array - */ - private zip(left: T[], right: G[], defaultZipValue: G) { - return left.map<[T, G]>((leftItem, index) => [leftItem, right[index] ?? defaultZipValue]); - } } diff --git a/x-pack/test/encrypted_saved_objects_api_integration/fixtures/api_consumer_plugin/server/index.ts b/x-pack/test/encrypted_saved_objects_api_integration/fixtures/api_consumer_plugin/server/index.ts index 0c40b795b5cc91..87bed7f4160191 100644 --- a/x-pack/test/encrypted_saved_objects_api_integration/fixtures/api_consumer_plugin/server/index.ts +++ b/x-pack/test/encrypted_saved_objects_api_integration/fixtures/api_consumer_plugin/server/index.ts @@ -62,10 +62,6 @@ export const plugin: PluginInitializer = publicPropertyExcludedFromAAD: { type: 'keyword' }, publicPropertyStoredEncrypted: { type: 'binary' }, privateProperty: { type: 'binary' }, - publicDeepProperty: { - enabled: false, - type: 'object', - }, }, }), }); @@ -116,31 +112,6 @@ export const plugin: PluginInitializer = } ); - for (const route of ['saved_objects', 'hidden_saved_objects']) { - router.put( - { - path: `/api/${route}/update-with-partial/{type}/{id}`, - validate: { params: (value) => ({ value }), body: (value) => ({ value }) }, - }, - async (context, request, response) => { - const [{ savedObjects }] = await core.getStartServices(); - const { type, id } = request.params; - const { attributes } = request.body as { attributes: any }; - - return response.ok({ - body: await savedObjects - .getScopedClient(request, { - includedHiddenTypes: [HIDDEN_SAVED_OBJECT_WITH_SECRET_TYPE], - excludedWrappers: ['encryptedSavedObjects'], - }) - .update(type, id, attributes, { - refresh: true, - }), - }); - } - ); - } - registerHiddenSORoutes(router, core, deps, [HIDDEN_SAVED_OBJECT_WITH_SECRET_TYPE]); }, start() {}, diff --git a/x-pack/test/encrypted_saved_objects_api_integration/tests/encrypted_saved_objects_api.ts b/x-pack/test/encrypted_saved_objects_api_integration/tests/encrypted_saved_objects_api.ts index 7c13cd646cb352..8bdc1715bf487b 100644 --- a/x-pack/test/encrypted_saved_objects_api_integration/tests/encrypted_saved_objects_api.ts +++ b/x-pack/test/encrypted_saved_objects_api_integration/tests/encrypted_saved_objects_api.ts @@ -41,7 +41,6 @@ export default function ({ getService }: FtrProviderContext) { publicPropertyStoredEncrypted: string; privateProperty: string; publicPropertyExcludedFromAAD: string; - publicDeepProperty: Record; }; let savedObject: SavedObject; @@ -51,7 +50,6 @@ export default function ({ getService }: FtrProviderContext) { publicPropertyStoredEncrypted: randomness.string(), privateProperty: randomness.string(), publicPropertyExcludedFromAAD: randomness.string(), - publicDeepProperty: { [randomness.string()]: randomness.string() }, }; const { body } = await supertest @@ -68,7 +66,6 @@ export default function ({ getService }: FtrProviderContext) { publicProperty: savedObjectOriginalAttributes.publicProperty, publicPropertyExcludedFromAAD: savedObjectOriginalAttributes.publicPropertyExcludedFromAAD, publicPropertyStoredEncrypted: savedObjectOriginalAttributes.publicPropertyStoredEncrypted, - publicDeepProperty: savedObjectOriginalAttributes.publicDeepProperty, }); const rawAttributes = await getRawSavedObjectAttributes(savedObject); @@ -76,9 +73,6 @@ export default function ({ getService }: FtrProviderContext) { expect(rawAttributes.publicPropertyExcludedFromAAD).to.be( savedObjectOriginalAttributes.publicPropertyExcludedFromAAD ); - expect(rawAttributes.publicDeepProperty).to.eql( - savedObjectOriginalAttributes.publicDeepProperty - ); expect(rawAttributes.publicPropertyStoredEncrypted).to.not.be.empty(); expect(rawAttributes.publicPropertyStoredEncrypted).to.not.be( @@ -214,7 +208,6 @@ export default function ({ getService }: FtrProviderContext) { publicProperty: savedObjectOriginalAttributes.publicProperty, publicPropertyExcludedFromAAD: savedObjectOriginalAttributes.publicPropertyExcludedFromAAD, publicPropertyStoredEncrypted: savedObjectOriginalAttributes.publicPropertyStoredEncrypted, - publicDeepProperty: savedObjectOriginalAttributes.publicDeepProperty, }); expect(response.error).to.be(undefined); }); @@ -224,13 +217,9 @@ export default function ({ getService }: FtrProviderContext) { // encrypted attributes. const updatedPublicProperty = randomness.string(); await supertest - .put( - `${getURLAPIBaseURL()}update-with-partial/${encryptedSavedObjectType}/${savedObject.id}` - ) + .put(`${getURLAPIBaseURL()}${encryptedSavedObjectType}/${savedObject.id}`) .set('kbn-xsrf', 'xxx') - .send({ - attributes: { publicProperty: updatedPublicProperty }, - }) + .send({ attributes: { publicProperty: updatedPublicProperty } }) .expect(200); const { body: response } = await supertest @@ -240,7 +229,6 @@ export default function ({ getService }: FtrProviderContext) { expect(response.attributes).to.eql({ publicProperty: updatedPublicProperty, publicPropertyExcludedFromAAD: savedObjectOriginalAttributes.publicPropertyExcludedFromAAD, - publicDeepProperty: savedObjectOriginalAttributes.publicDeepProperty, }); expect(response.error).to.eql({ message: 'Unable to decrypt attribute "publicPropertyStoredEncrypted"', @@ -260,7 +248,6 @@ export default function ({ getService }: FtrProviderContext) { publicProperty: savedObjectOriginalAttributes.publicProperty, publicPropertyExcludedFromAAD: savedObjectOriginalAttributes.publicPropertyExcludedFromAAD, publicPropertyStoredEncrypted: savedObjectOriginalAttributes.publicPropertyStoredEncrypted, - publicDeepProperty: savedObjectOriginalAttributes.publicDeepProperty, }); expect(savedObjects[0].error).to.be(undefined); }); @@ -270,9 +257,7 @@ export default function ({ getService }: FtrProviderContext) { // encrypted attributes. const updatedPublicProperty = randomness.string(); await supertest - .put( - `${getURLAPIBaseURL()}update-with-partial/${encryptedSavedObjectType}/${savedObject.id}` - ) + .put(`${getURLAPIBaseURL()}${encryptedSavedObjectType}/${savedObject.id}`) .set('kbn-xsrf', 'xxx') .send({ attributes: { publicProperty: updatedPublicProperty } }) .expect(200); @@ -288,7 +273,6 @@ export default function ({ getService }: FtrProviderContext) { expect(savedObjects[0].attributes).to.eql({ publicProperty: updatedPublicProperty, publicPropertyExcludedFromAAD: savedObjectOriginalAttributes.publicPropertyExcludedFromAAD, - publicDeepProperty: savedObjectOriginalAttributes.publicDeepProperty, }); expect(savedObjects[0].error).to.eql({ message: 'Unable to decrypt attribute "publicPropertyStoredEncrypted"', @@ -310,7 +294,6 @@ export default function ({ getService }: FtrProviderContext) { publicProperty: savedObjectOriginalAttributes.publicProperty, publicPropertyExcludedFromAAD: savedObjectOriginalAttributes.publicPropertyExcludedFromAAD, publicPropertyStoredEncrypted: savedObjectOriginalAttributes.publicPropertyStoredEncrypted, - publicDeepProperty: savedObjectOriginalAttributes.publicDeepProperty, }); expect(savedObjects[0].error).to.be(undefined); }); @@ -320,13 +303,9 @@ export default function ({ getService }: FtrProviderContext) { // encrypted attributes. const updatedPublicProperty = randomness.string(); await supertest - .put( - `${getURLAPIBaseURL()}update-with-partial/${encryptedSavedObjectType}/${savedObject.id}` - ) + .put(`${getURLAPIBaseURL()}${encryptedSavedObjectType}/${savedObject.id}`) .set('kbn-xsrf', 'xxx') - .send({ - attributes: { publicProperty: updatedPublicProperty }, - }) + .send({ attributes: { publicProperty: updatedPublicProperty } }) .expect(200); const { @@ -342,7 +321,6 @@ export default function ({ getService }: FtrProviderContext) { expect(savedObjects[0].attributes).to.eql({ publicProperty: updatedPublicProperty, publicPropertyExcludedFromAAD: savedObjectOriginalAttributes.publicPropertyExcludedFromAAD, - publicDeepProperty: savedObjectOriginalAttributes.publicDeepProperty, }); expect(savedObjects[0].error).to.eql({ message: 'Unable to decrypt attribute "publicPropertyStoredEncrypted"', @@ -355,7 +333,6 @@ export default function ({ getService }: FtrProviderContext) { publicPropertyExcludedFromAAD: randomness.string(), publicPropertyStoredEncrypted: randomness.string(), privateProperty: randomness.string(), - publicDeepProperty: { [randomness.string()]: randomness.string() }, }; const { body: response } = await supertest @@ -368,7 +345,6 @@ export default function ({ getService }: FtrProviderContext) { publicProperty: updatedAttributes.publicProperty, publicPropertyExcludedFromAAD: updatedAttributes.publicPropertyExcludedFromAAD, publicPropertyStoredEncrypted: updatedAttributes.publicPropertyStoredEncrypted, - publicDeepProperty: updatedAttributes.publicDeepProperty, }); const rawAttributes = await getRawSavedObjectAttributes(savedObject); @@ -385,32 +361,6 @@ export default function ({ getService }: FtrProviderContext) { expect(rawAttributes.privateProperty).to.not.be(updatedAttributes.privateProperty); }); - it('#update overwrites objects in their entirety', async () => { - const updatedAttributes = { - publicProperty: randomness.string(), - publicPropertyExcludedFromAAD: randomness.string(), - publicPropertyStoredEncrypted: randomness.string(), - privateProperty: randomness.string(), - publicDeepProperty: { [randomness.string()]: randomness.string() }, - }; - - await supertest - .put(`${getURLAPIBaseURL()}${encryptedSavedObjectType}/${savedObject.id}`) - .set('kbn-xsrf', 'xxx') - .send({ attributes: updatedAttributes }) - .expect(200); - - const { body: decryptedResponse } = await supertest - .get( - `${getURLAPIBaseURL()}get-decrypted-as-internal-user/${encryptedSavedObjectType}/${ - savedObject.id - }` - ) - .expect(200); - - expect(decryptedResponse.attributes).to.eql(updatedAttributes); - }); - it('#getDecryptedAsInternalUser decrypts and returns all attributes', async () => { const { body: decryptedResponse } = await supertest .get( @@ -427,9 +377,7 @@ export default function ({ getService }: FtrProviderContext) { const updatedAttributes = { publicPropertyExcludedFromAAD: randomness.string() }; const { body: response } = await supertest - .put( - `${getURLAPIBaseURL()}update-with-partial/${encryptedSavedObjectType}/${savedObject.id}` - ) + .put(`${getURLAPIBaseURL()}${encryptedSavedObjectType}/${savedObject.id}`) .set('kbn-xsrf', 'xxx') .send({ attributes: updatedAttributes }) .expect(200); @@ -456,9 +404,7 @@ export default function ({ getService }: FtrProviderContext) { const updatedAttributes = { publicProperty: randomness.string() }; const { body: response } = await supertest - .put( - `${getURLAPIBaseURL()}update-with-partial/${encryptedSavedObjectType}/${savedObject.id}` - ) + .put(`${getURLAPIBaseURL()}${encryptedSavedObjectType}/${savedObject.id}`) .set('kbn-xsrf', 'xxx') .send({ attributes: updatedAttributes }) .expect(200);