diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/apis/find.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/apis/find.test.ts index 4c28f2003a986..12b14bb6f1a32 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/apis/find.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/apis/find.test.ts @@ -344,6 +344,33 @@ describe('find', () => { ), }); }); + + it('does not perform migrations when a partial document is requested by specifying no fields should be retrieved', async () => { + const noNamespaceSearchResults = generateIndexPatternSearchResults(); + client.search.mockResolvedValueOnce( + elasticsearchClientMock.createSuccessTransportRequestPromise(noNamespaceSearchResults) + ); + migrator.migrateDocument.mockImplementationOnce((doc) => ({ ...doc, migrated: true })); + await expect( + repository.find({ + type, + fields: [], // don't return any fields + }) + ).resolves.not.toHaveProperty('saved_objects.0.migrated'); + expect(migrator.migrateDocument).not.toHaveBeenCalled(); + }); + + it('does not perform migrations when a partial document is requested by specifying some fields', async () => { + const noNamespaceSearchResults = generateIndexPatternSearchResults(); + client.search.mockResolvedValueOnce( + elasticsearchClientMock.createSuccessTransportRequestPromise(noNamespaceSearchResults) + ); + migrator.migrateDocument.mockImplementationOnce((doc) => ({ ...doc, migrated: true })); + await expect(repository.find({ type, fields: ['title'] })).resolves.not.toHaveProperty( + 'saved_objects.0.migrated' + ); + expect(migrator.migrateDocument).not.toHaveBeenCalled(); + }); }); describe('search dsl', () => { diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/apis/find.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/apis/find.ts index 1222425ac2150..40501a0d80a37 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/apis/find.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/apis/find.ts @@ -16,6 +16,7 @@ import { CheckAuthorizationResult, SavedObjectsRawDocSource, GetFindRedactTypeMapParams, + SavedObjectUnsanitizedDoc, } from '@kbn/core-saved-objects-server'; import { DEFAULT_NAMESPACE_STRING, @@ -57,6 +58,7 @@ export const performFind = async ( common: commonHelper, serializer: serializerHelper, migration: migrationHelper, + encryption: encryptionHelper, } = helpers; const { securityExtension, spacesExtension } = extensions; let namespaces!: string[]; @@ -266,13 +268,27 @@ export const performFind = async ( const migratedDocuments: Array> = []; try { for (const savedObject of savedObjects) { + let migrated: SavedObjectUnsanitizedDoc | undefined; const { sort, score, ...rawObject } = savedObject; - const migrated = disableExtensions - ? migrationHelper.migrateStorageDocument(rawObject) - : await migrationHelper.migrateAndDecryptStorageDocument({ - document: rawObject, - typeMap: redactTypeMap, - }); + + if (fields !== undefined) { + // If the fields argument is set, don't migrate. + // This document may only contains a subset of it's fields meaning the migration + // (transform and forwardCompatibilitySchema) is not guaranteed to succeed. We + // still try to decrypt/redact the fields that are present in the document. + migrated = await encryptionHelper.optionallyDecryptAndRedactSingleResult( + savedObject, + redactTypeMap + ); + } else if (disableExtensions) { + migrated = migrationHelper.migrateStorageDocument(rawObject); + } else { + migrated = await migrationHelper.migrateAndDecryptStorageDocument({ + document: rawObject, + typeMap: redactTypeMap, + }); + } + migratedDocuments.push({ ...migrated, sort, score } as SavedObjectsFindResult); } } catch (error) {