From 5819cfb1bf9affe6c10b69a347a4c27755e99a78 Mon Sep 17 00:00:00 2001 From: Thom Heymann <190132+thomheymann@users.noreply.github.com> Date: Tue, 25 Jan 2022 16:26:53 +0000 Subject: [PATCH] Add audit logging to space deletion (#123378) * Add audit logging to space deletion * Fix outcome * Delete all non-global saved objects * Added suggestions from code review * Fix tests --- .../server/authorization/audit_logger.test.ts | 1 + .../authorization/authorization.test.ts | 1 + .../alerts_client_factory.test.ts | 1 + .../tests/bulk_update.test.ts | 1 + .../tests/find_alerts.test.ts | 1 + .../alert_data_client/tests/get.test.ts | 1 + .../alert_data_client/tests/update.test.ts | 1 + .../security/server/audit/audit_events.ts | 6 --- .../server/audit/audit_service.test.ts | 1 + .../security/server/audit/audit_service.ts | 14 +++++- .../security/server/audit/index.mock.ts | 2 + .../authentication/authenticator.test.ts | 4 ++ x-pack/plugins/security/server/plugin.test.ts | 1 + .../secure_spaces_client_wrapper.test.ts | 43 +++++++++++++++++-- .../spaces/secure_spaces_client_wrapper.ts | 34 +++++++++++++++ .../spaces_client/spaces_client.mock.ts | 12 ++++-- .../spaces_client/spaces_client.test.ts | 26 +++++++---- .../server/spaces_client/spaces_client.ts | 23 +++++++++- .../spaces_client/spaces_client_service.ts | 14 ++++-- 19 files changed, 158 insertions(+), 29 deletions(-) diff --git a/x-pack/plugins/cases/server/authorization/audit_logger.test.ts b/x-pack/plugins/cases/server/authorization/audit_logger.test.ts index 48c6e9ebcd07a..c2f00e8cfff05 100644 --- a/x-pack/plugins/cases/server/authorization/audit_logger.test.ts +++ b/x-pack/plugins/cases/server/authorization/audit_logger.test.ts @@ -32,6 +32,7 @@ describe('audit_logger', () => { describe('log function', () => { const mockLogger: jest.Mocked = { log: jest.fn(), + enabled: true, }; let logger: AuthorizationAuditLogger; diff --git a/x-pack/plugins/cases/server/authorization/authorization.test.ts b/x-pack/plugins/cases/server/authorization/authorization.test.ts index f644f7366100b..693277161c330 100644 --- a/x-pack/plugins/cases/server/authorization/authorization.test.ts +++ b/x-pack/plugins/cases/server/authorization/authorization.test.ts @@ -24,6 +24,7 @@ describe('authorization', () => { request = httpServerMock.createKibanaRequest(); mockLogger = { log: jest.fn(), + enabled: true, }; }); diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client_factory.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client_factory.test.ts index 276ea070d6f87..af531e8ae8e12 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client_factory.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client_factory.test.ts @@ -46,6 +46,7 @@ const fakeRequest = { const auditLogger = { log: jest.fn(), + enabled: true, } as jest.Mocked; describe('AlertsClientFactory', () => { diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/bulk_update.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/bulk_update.test.ts index 92f5ea4517d3f..09861278cd5d5 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/bulk_update.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/bulk_update.test.ts @@ -25,6 +25,7 @@ const alertingAuthMock = alertingAuthorizationMock.create(); const esClientMock = elasticsearchClientMock.createElasticsearchClient(); const auditLogger = { log: jest.fn(), + enabled: true, } as jest.Mocked; const alertsClientParams: jest.Mocked = { diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/find_alerts.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/find_alerts.test.ts index 5f9a20c14ea5b..bfff95b5d601b 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/find_alerts.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/find_alerts.test.ts @@ -24,6 +24,7 @@ const alertingAuthMock = alertingAuthorizationMock.create(); const esClientMock = elasticsearchClientMock.createElasticsearchClient(); const auditLogger = { log: jest.fn(), + enabled: true, } as jest.Mocked; const alertsClientParams: jest.Mocked = { diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts index eaf6c0089ce12..0c74cc1463410 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts @@ -25,6 +25,7 @@ const alertingAuthMock = alertingAuthorizationMock.create(); const esClientMock = elasticsearchClientMock.createElasticsearchClient(); const auditLogger = { log: jest.fn(), + enabled: true, } as jest.Mocked; const alertsClientParams: jest.Mocked = { diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts index 85527e26a9cd3..0dcfc602bc281 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts @@ -24,6 +24,7 @@ const alertingAuthMock = alertingAuthorizationMock.create(); const esClientMock = elasticsearchClientMock.createElasticsearchClient(); const auditLogger = { log: jest.fn(), + enabled: true, } as jest.Mocked; const alertsClientParams: jest.Mocked = { diff --git a/x-pack/plugins/security/server/audit/audit_events.ts b/x-pack/plugins/security/server/audit/audit_events.ts index 2dfaf8ece004f..37b2cecfa55c1 100644 --- a/x-pack/plugins/security/server/audit/audit_events.ts +++ b/x-pack/plugins/security/server/audit/audit_events.ts @@ -218,8 +218,6 @@ export enum SavedObjectAction { UPDATE = 'saved_object_update', DELETE = 'saved_object_delete', FIND = 'saved_object_find', - ADD_TO_SPACES = 'saved_object_add_to_spaces', - DELETE_FROM_SPACES = 'saved_object_delete_from_spaces', REMOVE_REFERENCES = 'saved_object_remove_references', OPEN_POINT_IN_TIME = 'saved_object_open_point_in_time', CLOSE_POINT_IN_TIME = 'saved_object_close_point_in_time', @@ -236,8 +234,6 @@ const savedObjectAuditVerbs: Record = { saved_object_update: ['update', 'updating', 'updated'], saved_object_delete: ['delete', 'deleting', 'deleted'], saved_object_find: ['access', 'accessing', 'accessed'], - saved_object_add_to_spaces: ['update', 'updating', 'updated'], - saved_object_delete_from_spaces: ['update', 'updating', 'updated'], saved_object_open_point_in_time: [ 'open point-in-time', 'opening point-in-time', @@ -272,8 +268,6 @@ const savedObjectAuditTypes: Record = { saved_object_update: 'change', saved_object_delete: 'deletion', saved_object_find: 'access', - saved_object_add_to_spaces: 'change', - saved_object_delete_from_spaces: 'change', saved_object_open_point_in_time: 'creation', saved_object_close_point_in_time: 'deletion', saved_object_remove_references: 'change', diff --git a/x-pack/plugins/security/server/audit/audit_service.test.ts b/x-pack/plugins/security/server/audit/audit_service.test.ts index 1815f617dceae..eb1a22e0b3543 100644 --- a/x-pack/plugins/security/server/audit/audit_service.test.ts +++ b/x-pack/plugins/security/server/audit/audit_service.test.ts @@ -68,6 +68,7 @@ describe('#setup', () => { Object { "asScoped": [Function], "withoutRequest": Object { + "enabled": true, "log": [Function], }, } diff --git a/x-pack/plugins/security/server/audit/audit_service.ts b/x-pack/plugins/security/server/audit/audit_service.ts index a29ec221b3474..6f81164be5a89 100644 --- a/x-pack/plugins/security/server/audit/audit_service.ts +++ b/x-pack/plugins/security/server/audit/audit_service.ts @@ -49,6 +49,14 @@ export interface AuditLogger { * ``` */ log: (event: AuditEvent | undefined) => void; + + /** + * Indicates whether audit logging is enabled or not. + * + * Useful for skipping resource-intense operations that don't need to be performed when audit + * logging is disabled. + */ + readonly enabled: boolean; } export interface AuditServiceSetup { @@ -122,7 +130,8 @@ export class AuditService { ); // Record feature usage at a regular interval if enabled and license allows - if (config.enabled && config.appender) { + const enabled = !!(config.enabled && config.appender); + if (enabled) { license.features$.subscribe((features) => { clearInterval(this.usageIntervalId!); if (features.allowAuditLogging) { @@ -169,6 +178,7 @@ export class AuditService { trace: { id: request.id }, }); }, + enabled, }); http.registerOnPostAuth((request, response, t) => { @@ -180,7 +190,7 @@ export class AuditService { return { asScoped, - withoutRequest: { log }, + withoutRequest: { log, enabled }, }; } diff --git a/x-pack/plugins/security/server/audit/index.mock.ts b/x-pack/plugins/security/server/audit/index.mock.ts index c84faacff0147..6ac9108b51a83 100644 --- a/x-pack/plugins/security/server/audit/index.mock.ts +++ b/x-pack/plugins/security/server/audit/index.mock.ts @@ -13,9 +13,11 @@ export const auditServiceMock = { getLogger: jest.fn(), asScoped: jest.fn().mockReturnValue({ log: jest.fn(), + enabled: true, }), withoutRequest: { log: jest.fn(), + enabled: true, }, } as jest.Mocked>; }, diff --git a/x-pack/plugins/security/server/authentication/authenticator.test.ts b/x-pack/plugins/security/server/authentication/authenticator.test.ts index 62ca6168584fb..3685ea28c08b7 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.test.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.test.ts @@ -264,6 +264,7 @@ describe('Authenticator', () => { let mockSessVal: SessionValue; const auditLogger = { log: jest.fn(), + enabled: true, }; beforeEach(() => { @@ -1094,6 +1095,7 @@ describe('Authenticator', () => { let mockSessVal: SessionValue; const auditLogger = { log: jest.fn(), + enabled: true, }; beforeEach(() => { @@ -2009,6 +2011,7 @@ describe('Authenticator', () => { let mockSessVal: SessionValue; const auditLogger = { log: jest.fn(), + enabled: true, }; beforeEach(() => { @@ -2145,6 +2148,7 @@ describe('Authenticator', () => { let mockSessionValue: SessionValue; const auditLogger = { log: jest.fn(), + enabled: true, }; beforeEach(() => { diff --git a/x-pack/plugins/security/server/plugin.test.ts b/x-pack/plugins/security/server/plugin.test.ts index 85c2fff5a438e..f4294ecbb6c11 100644 --- a/x-pack/plugins/security/server/plugin.test.ts +++ b/x-pack/plugins/security/server/plugin.test.ts @@ -68,6 +68,7 @@ describe('Security Plugin', () => { "audit": Object { "asScoped": [Function], "withoutRequest": Object { + "enabled": false, "log": [Function], }, }, diff --git a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts index 7f62948251d7c..2e39810d4cbde 100644 --- a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts +++ b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts @@ -8,7 +8,11 @@ import { mockEnsureAuthorized } from './secure_spaces_client_wrapper.test.mocks'; import { deepFreeze } from '@kbn/std'; -import type { EcsEventOutcome, SavedObjectsClientContract } from 'src/core/server'; +import type { + EcsEventOutcome, + SavedObjectsClientContract, + SavedObjectsFindResponse, +} from 'src/core/server'; import { SavedObjectsErrorHelpers } from 'src/core/server'; import { httpServerMock } from 'src/core/server/mocks'; @@ -63,6 +67,31 @@ const setup = ({ securityEnabled = false }: Opts = {}) => { return space; }); + baseClient.createSavedObjectFinder.mockImplementation(() => ({ + async *find() { + yield { + saved_objects: [ + { + namespaces: ['*'], + type: 'dashboard', + id: '1', + }, + { + namespaces: ['existing_space'], + type: 'dashboard', + id: '2', + }, + { + namespaces: ['default', 'existing_space'], + type: 'dashboard', + id: '3', + }, + ], + } as SavedObjectsFindResponse; + }, + async close() {}, + })); + const authorization = authorizationMock.create({ version: 'unit-test', applicationName: 'kibana', @@ -602,7 +631,7 @@ describe('SecureSpacesClientWrapper', () => { }); }); - test(`throws a forbidden error when unauthorized`, async () => { + it(`throws a forbidden error when unauthorized`, async () => { const username = 'some_user'; const { wrapper, baseClient, authorization, auditLogger, request } = setup({ @@ -637,7 +666,7 @@ describe('SecureSpacesClientWrapper', () => { }); }); - it('deletes the space when authorized', async () => { + it('deletes the space with all saved objects when authorized', async () => { const username = 'some_user'; const { wrapper, baseClient, authorization, auditLogger, request } = setup({ @@ -669,6 +698,14 @@ describe('SecureSpacesClientWrapper', () => { type: 'space', id: space.id, }); + expectAuditEvent(auditLogger, SavedObjectAction.DELETE, 'unknown', { + type: 'dashboard', + id: '2', + }); + expectAuditEvent(auditLogger, SavedObjectAction.UPDATE_OBJECTS_SPACES, 'unknown', { + type: 'dashboard', + id: '3', + }); }); }); diff --git a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts index 9d20a6ea40b24..c43216643205f 100644 --- a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts +++ b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts @@ -17,6 +17,7 @@ import type { LegacyUrlAliasTarget, Space, } from '../../../spaces/server'; +import { ALL_SPACES_ID } from '../../common/constants'; import type { AuditLogger } from '../audit'; import { SavedObjectAction, savedObjectEvent, SpaceAuditAction, spaceAuditEvent } from '../audit'; import type { AuthorizationServiceSetup } from '../authorization'; @@ -246,6 +247,10 @@ export class SecureSpacesClientWrapper implements ISpacesClient { return this.spacesClient.update(id, space); } + public createSavedObjectFinder(id: string) { + return this.spacesClient.createSavedObjectFinder(id); + } + public async delete(id: string) { if (this.useRbac) { try { @@ -265,6 +270,35 @@ export class SecureSpacesClientWrapper implements ISpacesClient { } } + // Fetch saved objects to be removed for audit logging + if (this.auditLogger.enabled) { + const finder = this.spacesClient.createSavedObjectFinder(id); + try { + for await (const response of finder.find()) { + response.saved_objects.forEach((savedObject) => { + const { namespaces = [] } = savedObject; + const isOnlySpace = namespaces.length === 1; // We can always rely on the `namespaces` field having >=1 element + if (namespaces.includes(ALL_SPACES_ID) && !namespaces.includes(id)) { + // This object exists in All Spaces and its `namespaces` field isn't going to change; there's nothing to audit + return; + } + this.auditLogger.log( + savedObjectEvent({ + action: isOnlySpace + ? SavedObjectAction.DELETE + : SavedObjectAction.UPDATE_OBJECTS_SPACES, + outcome: 'unknown', + savedObject: { type: savedObject.type, id: savedObject.id }, + deleteFromSpaces: [id], + }) + ); + }); + } + } finally { + await finder.close(); + } + } + this.auditLogger.log( spaceAuditEvent({ action: SpaceAuditAction.DELETE, diff --git a/x-pack/plugins/spaces/server/spaces_client/spaces_client.mock.ts b/x-pack/plugins/spaces/server/spaces_client/spaces_client.mock.ts index ed47aed72fba2..97a26447ad297 100644 --- a/x-pack/plugins/spaces/server/spaces_client/spaces_client.mock.ts +++ b/x-pack/plugins/spaces/server/spaces_client/spaces_client.mock.ts @@ -5,12 +5,15 @@ * 2.0. */ +import { savedObjectsRepositoryMock } from 'src/core/server/mocks'; + import type { Space } from '../../common'; import { DEFAULT_SPACE_ID } from '../../common/constants'; import type { SpacesClient } from './spaces_client'; -const createSpacesClientMock = () => - ({ +const createSpacesClientMock = () => { + const repositoryMock = savedObjectsRepositoryMock.create(); + return { getAll: jest.fn().mockResolvedValue([ { id: DEFAULT_SPACE_ID, @@ -28,10 +31,11 @@ const createSpacesClientMock = () => }), create: jest.fn().mockImplementation((space: Space) => Promise.resolve(space)), update: jest.fn().mockImplementation((space: Space) => Promise.resolve(space)), + createSavedObjectFinder: repositoryMock.createPointInTimeFinder, delete: jest.fn(), disableLegacyUrlAliases: jest.fn(), - } as unknown as jest.Mocked); - + } as unknown as jest.Mocked; +}; export const spacesClientMock = { create: createSpacesClientMock, }; diff --git a/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts b/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts index e594306f5ee3a..86b2886d2461a 100644 --- a/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts +++ b/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts @@ -77,7 +77,7 @@ describe('#getAll', () => { } as any); const mockConfig = createMockConfig({ maxSpaces: 1234 }); - const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository); + const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository, []); const actualSpaces = await client.getAll(); expect(actualSpaces).toEqual(expectedSpaces); @@ -90,7 +90,10 @@ describe('#getAll', () => { }); test(`throws Boom.badRequest when an invalid purpose is provided'`, async () => { - const client = new SpacesClient(null as any, null as any, null as any); + const mockDebugLogger = createMockDebugLogger(); + const mockCallWithRequestRepository = savedObjectsRepositoryMock.create(); + const mockConfig = createMockConfig(); + const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository, []); await expect( client.getAll({ purpose: 'invalid_purpose' as GetAllSpacesPurpose }) ).rejects.toThrowErrorMatchingInlineSnapshot(`"unsupported space purpose: invalid_purpose"`); @@ -122,7 +125,7 @@ describe('#get', () => { const mockCallWithRequestRepository = savedObjectsRepositoryMock.create(); mockCallWithRequestRepository.get.mockResolvedValue(savedObject); - const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository); + const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository, []); const id = savedObject.id; const actualSpace = await client.get(id); @@ -181,7 +184,7 @@ describe('#create', () => { const mockConfig = createMockConfig({ maxSpaces }); - const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository); + const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository, []); const actualSpace = await client.create(spaceToCreate); @@ -207,7 +210,7 @@ describe('#create', () => { const mockConfig = createMockConfig({ maxSpaces }); - const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository); + const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository, []); expect(client.create(spaceToCreate)).rejects.toThrowErrorMatchingInlineSnapshot( `"Unable to create Space, this exceeds the maximum number of spaces set by the xpack.spaces.maxSpaces setting"` @@ -267,7 +270,7 @@ describe('#update', () => { const mockCallWithRequestRepository = savedObjectsRepositoryMock.create(); mockCallWithRequestRepository.get.mockResolvedValue(savedObject); - const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository); + const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository, []); const id = savedObject.id; const actualSpace = await client.update(id, spaceToUpdate); @@ -309,7 +312,7 @@ describe('#delete', () => { const mockCallWithRequestRepository = savedObjectsRepositoryMock.create(); mockCallWithRequestRepository.get.mockResolvedValue(reservedSavedObject); - const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository); + const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository, []); expect(client.delete(id)).rejects.toThrowErrorMatchingInlineSnapshot( `"The foo space cannot be deleted because it is reserved."` @@ -324,7 +327,7 @@ describe('#delete', () => { const mockCallWithRequestRepository = savedObjectsRepositoryMock.create(); mockCallWithRequestRepository.get.mockResolvedValue(notReservedSavedObject); - const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository); + const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository, []); await client.delete(id); @@ -339,7 +342,12 @@ describe('#delete', () => { const mockConfig = createMockConfig(); const mockCallWithRequestRepository = savedObjectsRepositoryMock.create(); - const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository); + const client = new SpacesClient( + mockDebugLogger, + mockConfig, + mockCallWithRequestRepository, + [] + ); const aliases = [ { targetSpace: 'space1', targetType: 'foo', sourceId: '123' }, { targetSpace: 'space2', targetType: 'bar', sourceId: '456' }, diff --git a/x-pack/plugins/spaces/server/spaces_client/spaces_client.ts b/x-pack/plugins/spaces/server/spaces_client/spaces_client.ts index 0a91c7aff1a08..2bd51a13bc642 100644 --- a/x-pack/plugins/spaces/server/spaces_client/spaces_client.ts +++ b/x-pack/plugins/spaces/server/spaces_client/spaces_client.ts @@ -8,7 +8,11 @@ import Boom from '@hapi/boom'; import { omit } from 'lodash'; -import type { ISavedObjectsRepository, SavedObject } from 'src/core/server'; +import type { + ISavedObjectsPointInTimeFinder, + ISavedObjectsRepository, + SavedObject, +} from 'src/core/server'; import type { GetAllSpacesOptions, @@ -58,6 +62,13 @@ export interface ISpacesClient { */ update(id: string, space: Space): Promise; + /** + * Returns a {@link ISavedObjectsPointInTimeFinder} to help page through + * saved objects within the specified space. + * @param id the id of the space to search. + */ + createSavedObjectFinder(id: string): ISavedObjectsPointInTimeFinder; + /** * Deletes a space, and all saved objects belonging to that space. * @param id the id of the space to delete. @@ -78,7 +89,8 @@ export class SpacesClient implements ISpacesClient { constructor( private readonly debugLogger: (message: string) => void, private readonly config: ConfigType, - private readonly repository: ISavedObjectsRepository + private readonly repository: ISavedObjectsRepository, + private readonly nonGlobalTypeNames: string[] ) {} public async getAll(options: GetAllSpacesOptions = {}): Promise { @@ -136,6 +148,13 @@ export class SpacesClient implements ISpacesClient { return this.transformSavedObjectToSpace(updatedSavedObject); } + public createSavedObjectFinder(id: string) { + return this.repository.createPointInTimeFinder({ + type: this.nonGlobalTypeNames, + namespaces: [id], + }); + } + public async delete(id: string) { const existingSavedObject = await this.repository.get('space', id); if (isReservedSpace(this.transformSavedObjectToSpace(existingSavedObject))) { diff --git a/x-pack/plugins/spaces/server/spaces_client/spaces_client_service.ts b/x-pack/plugins/spaces/server/spaces_client/spaces_client_service.ts index 6580a2d57f040..cf7766decc2dd 100644 --- a/x-pack/plugins/spaces/server/spaces_client/spaces_client_service.ts +++ b/x-pack/plugins/spaces/server/spaces_client/spaces_client_service.ts @@ -96,20 +96,28 @@ export class SpacesClientService { } public start(coreStart: CoreStart): SpacesClientServiceStart { + const nonGlobalTypes = coreStart.savedObjects + .getTypeRegistry() + .getAllTypes() + .filter((x) => x.namespaceType !== 'agnostic'); + const nonGlobalTypeNames = nonGlobalTypes.map((x) => x.name); + if (!this.repositoryFactory) { + const hiddenTypeNames = nonGlobalTypes.filter((x) => x.hidden).map((x) => x.name); this.repositoryFactory = (request, savedObjectsStart) => - savedObjectsStart.createScopedRepository(request, ['space']); + savedObjectsStart.createScopedRepository(request, [...hiddenTypeNames, 'space']); } + return { createSpacesClient: (request: KibanaRequest) => { if (!this.config) { throw new Error('Initialization error: spaces config is not available'); } - const baseClient = new SpacesClient( this.debugLogger, this.config, - this.repositoryFactory!(request, coreStart.savedObjects) + this.repositoryFactory!(request, coreStart.savedObjects), + nonGlobalTypeNames ); if (this.clientWrapper) { return this.clientWrapper(request, baseClient);