import semver from 'semver' ; import type { SavedObjectMigrationContext , SavedObjectMigrationFn , SavedObjectUnsanitizedDoc , } from 'src/core/server' ; import { EncryptionError } from './crypto' ; import type { EncryptedSavedObjectsService , EncryptedSavedObjectTypeRegistration } from './crypto' ; import { normalizeNamespace } from './saved_objects' ; type SavedObjectOptionalMigrationFn < InputAttributes , MigratedAttributes > = ( doc : SavedObjectUnsanitizedDoc < InputAttributes > | SavedObjectUnsanitizedDoc < MigratedAttributes > , context : SavedObjectMigrationContext ) => SavedObjectUnsanitizedDoc < MigratedAttributes > ; export type IsMigrationNeededPredicate < InputAttributes , MigratedAttributes > = ( encryptedDoc : SavedObjectUnsanitizedDoc < InputAttributes > | SavedObjectUnsanitizedDoc < MigratedAttributes > ) => encryptedDoc is SavedObjectUnsanitizedDoc < InputAttributes > ; export interface CreateEncryptedSavedObjectsMigrationFnOpts < InputAttributes = unknown , MigratedAttributes = InputAttributes > { isMigrationNeededPredicate : IsMigrationNeededPredicate < InputAttributes , MigratedAttributes > ; migration : SavedObjectMigrationFn < InputAttributes , MigratedAttributes > ; shouldMigrateIfDecryptionFails ? : boolean ; inputType ? : EncryptedSavedObjectTypeRegistration ; migratedType ? : EncryptedSavedObjectTypeRegistration ; } export type CreateEncryptedSavedObjectsMigrationFn = < InputAttributes = unknown , MigratedAttributes = InputAttributes > ( opts : CreateEncryptedSavedObjectsMigrationFnOpts < InputAttributes , MigratedAttributes > ) => SavedObjectOptionalMigrationFn < InputAttributes , MigratedAttributes > ; export const getCreateMigration = ( encryptedSavedObjectsService , instantiateServiceWithLegacyType ) : CreateEncryptedSavedObjectsMigrationFn => ( opts ) => { const { isMigrationNeededPredicate , migration , shouldMigrateIfDecryptionFails , inputType , migratedType , } = opts ; if ( inputType && migratedType && inputType . type !== migratedType . type ) { throw new Error ( `An Invalid Encrypted Saved Objects migration is trying to migrate across types ("${ inputType . type }" => "${ migratedType . type }"), which isn't permitted` ) ; } const inputService = inputType ? instantiateServiceWithLegacyType ( inputType ) : encryptedSavedObjectsService ; const migratedService = migratedType ? instantiateServiceWithLegacyType ( migratedType ) : encryptedSavedObjectsService ; return ( encryptedDoc , context ) => { if ( ! isMigrationNeededPredicate ( encryptedDoc ) ) { return encryptedDoc ; } const { type , id , originId } = encryptedDoc ; const isTypeBeingConverted = ! ! context . convertToMultiNamespaceTypeVersion && semver . lte ( context . migrationVersion , context . convertToMultiNamespaceTypeVersion ) ; const descriptorNamespace = context . isSingleNamespaceType ? encryptedDoc . namespace : undefined ; let decryptDescriptorNamespace = descriptorNamespace ; if ( isTypeBeingConverted ) { decryptDescriptorNamespace = encryptedDoc . namespaces ?. length ? normalizeNamespace ( encryptedDoc . namespaces [ 0 ] ) : encryptedDoc . namespace ; } const decryptDescriptor = { id , type , namespace : decryptDescriptorNamespace } ; const encryptDescriptor = { id , type , namespace : descriptorNamespace } ; const documentToMigrate = mapAttributes ( encryptedDoc , ( inputAttributes ) => { try { return inputService . decryptAttributesSync < any > ( decryptDescriptor , inputAttributes , { isTypeBeingConverted , originId , } ) ; } catch ( err ) { if ( ! shouldMigrateIfDecryptionFails || ! ( err instanceof EncryptionError ) ) { throw err ; } context . log . warn ( `Decryption failed for encrypted Saved Object "${ encryptedDoc . id }" of type "${ encryptedDoc . type }" with error: ${ err . message }. Encrypted attributes have been stripped from the original document and migration will be applied but this may cause errors later on.` ) ; return inputService . stripOrDecryptAttributesSync < any > ( decryptDescriptor , inputAttributes , { isTypeBeingConverted , originId , } ) . attributes ; } } ) ; return mapAttributes ( migration ( documentToMigrate , context ) , ( migratedAttributes ) => { return migratedService . encryptAttributesSync < any > ( encryptDescriptor , migratedAttributes ) ; } ) ; } ; } ; function mapAttributes < T > ( obj , mapper ) { return Object . assign ( obj , { attributes : mapper ( obj . attributes ) , } ) ; }