Skip to content

Commit

Permalink
Initial implementation of SO type validation.
Browse files Browse the repository at this point in the history
  • Loading branch information
lukeelmers committed Nov 17, 2021
1 parent dfa856e commit 1848c92
Show file tree
Hide file tree
Showing 10 changed files with 591 additions and 7 deletions.
4 changes: 4 additions & 0 deletions src/core/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,10 @@ export type {
SavedObjectsImportSimpleWarning,
SavedObjectsImportActionRequiredWarning,
SavedObjectsImportWarning,
SavedObjectsValidationError,
SavedObjectsValidationFunction,
SavedObjectsValidationMap,
SavedObjectsValidationSpec,
} from './saved_objects';

export type {
Expand Down
7 changes: 7 additions & 0 deletions src/core/server/saved_objects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ export type {
SavedObjectTypeExcludeFromUpgradeFilterHook,
} from './types';

export type {
SavedObjectsValidationMap,
SavedObjectsValidationSpec,
SavedObjectsValidationFunction,
} from './validation';
export { SavedObjectsValidationError } from './validation';

export { savedObjectsConfig, savedObjectsMigrationConfig } from './saved_objects_config';
export { SavedObjectTypeRegistry } from './saved_objects_type_registry';
export type { ISavedObjectTypeRegistry } from './saved_objects_type_registry';
16 changes: 14 additions & 2 deletions src/core/server/saved_objects/saved_objects_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@ import {
SavedObjectConfig,
} from './saved_objects_config';
import { KibanaRequest, InternalHttpServiceSetup } from '../http';
import { SavedObjectsClientContract, SavedObjectsType, SavedObjectStatusMeta } from './types';
import {
SavedObjectsClientContract,
SavedObjectsType,
SavedObjectStatusMeta,
SavedObjectAttributes,
} from './types';
import { ISavedObjectsRepository, SavedObjectsRepository } from './service/lib/repository';
import {
SavedObjectsClientFactoryProvider,
Expand Down Expand Up @@ -112,6 +117,7 @@ export interface SavedObjectsServiceSetup {
* // src/plugins/my_plugin/server/saved_objects/my_type.ts
* import { SavedObjectsType } from 'src/core/server';
* import * as migrations from './migrations';
* import * as schemas from './schemas';
*
* export const myType: SavedObjectsType = {
* name: 'MyType',
Expand All @@ -131,6 +137,10 @@ export interface SavedObjectsServiceSetup {
* '2.0.0': migrations.migrateToV2,
* '2.1.0': migrations.migrateToV2_1
* },
* schemas: {
* '2.0.0': schemas.v2,
* '2.1.0': schemas.v2_1,
* },
* };
*
* // src/plugins/my_plugin/server/plugin.ts
Expand All @@ -144,7 +154,9 @@ export interface SavedObjectsServiceSetup {
* }
* ```
*/
registerType: <Attributes = any>(type: SavedObjectsType<Attributes>) => void;
registerType: <Attributes extends SavedObjectAttributes = any>(
type: SavedObjectsType<Attributes>
) => void;

/**
* Returns the default index used for saved objects.
Expand Down
25 changes: 24 additions & 1 deletion src/core/server/saved_objects/service/lib/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import {
SavedObjectsMigrationVersion,
MutatingOperationRefreshSetting,
} from '../../types';
import { SavedObjectsTypeValidator } from '../../validation';
import { ISavedObjectTypeRegistry } from '../../saved_objects_type_registry';
import { internalBulkResolve, InternalBulkResolveError } from './internal_bulk_resolve';
import { validateConvertFilterToKueryNode } from './filter_utils';
Expand Down Expand Up @@ -363,7 +364,15 @@ export class SavedObjectsRepository {
...(Array.isArray(references) && { references }),
});

const raw = this._serializer.savedObjectToRaw(migrated as SavedObjectSanitizedDoc);
/**
* If a validation has been registered for this type, we run it against the migrated attributes.
* This is an imperfect solution because malformed attributes could have already caused the
* migration to fail, but it's the best we can do without devising a way to run validations
* inside the migration algorithm itself.
*/
this.validateObjectAttributes<T>(type, migrated as SavedObjectSanitizedDoc<T>);

const raw = this._serializer.savedObjectToRaw(migrated as SavedObjectSanitizedDoc<T>);

const requestParams = {
id: raw._id,
Expand Down Expand Up @@ -2196,6 +2205,20 @@ export class SavedObjectsRepository {
);
}
}

/** Validate a migrated doc against the registered saved object type's schema. */
private validateObjectAttributes<T>(type: string, doc: SavedObjectSanitizedDoc<T>) {
const savedObjectType = this._registry.getType(type);
if (!savedObjectType?.schemas) {
return;
}

const validator = new SavedObjectsTypeValidator({
type,
validationMap: savedObjectType.schemas,
});
validator.validate(this._migrator.kibanaVersion, doc.attributes);
}
}

/**
Expand Down
15 changes: 11 additions & 4 deletions src/core/server/saved_objects/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { SavedObjectsTypeMappingDefinition } from './mappings';
import { SavedObjectMigrationMap } from './migrations';
import { SavedObjectsExportTransform } from './export';
import { SavedObjectsImportHook } from './import/types';
import { SavedObjectsValidationMap } from './validation';

export type {
SavedObjectsImportResponse,
Expand All @@ -28,7 +29,7 @@ export type {
SavedObjectsImportWarning,
} from './import/types';

import { SavedObject } from '../../types';
import { SavedObject, SavedObjectAttributes } from '../../types';
import { ElasticsearchClient } from '../elasticsearch';

type KueryNode = any;
Expand Down Expand Up @@ -250,11 +251,9 @@ export type SavedObjectsClientContract = Pick<SavedObjectsClient, keyof SavedObj
export type SavedObjectsNamespaceType = 'single' | 'multiple' | 'multiple-isolated' | 'agnostic';

/**
* @remarks This is only internal for now, and will only be public when we expose the registerType API
*
* @public
*/
export interface SavedObjectsType<Attributes = any> {
export interface SavedObjectsType<Attributes extends SavedObjectAttributes = any> {
/**
* The name of the type, which is also used as the internal id.
*/
Expand Down Expand Up @@ -291,6 +290,14 @@ export interface SavedObjectsType<Attributes = any> {
* An optional map of {@link SavedObjectMigrationFn | migrations} or a function returning a map of {@link SavedObjectMigrationFn | migrations} to be used to migrate the type.
*/
migrations?: SavedObjectMigrationMap | (() => SavedObjectMigrationMap);
/**
* An optional schema that can be used to validate the attributes of the type.
*
* When provided, calls to {@link SavedObjectsClient.create | create} will be validated against this schema.
*
* See {@link SavedObjectsValidationMap} for more details.
*/
schemas?: SavedObjectsValidationMap<Attributes> | (() => SavedObjectsValidationMap<Attributes>);
/**
* If defined, objects of this type will be converted to a 'multiple' or 'multiple-isolated' namespace type when migrating to this
* version.
Expand Down
15 changes: 15 additions & 0 deletions src/core/server/saved_objects/validation/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

export { SavedObjectsTypeValidator } from './validator';
export type {
SavedObjectsValidationMap,
SavedObjectsValidationSpec,
SavedObjectsValidationFunction,
} from './validator';
export { SavedObjectsValidationError } from './validator_error';
Loading

0 comments on commit 1848c92

Please sign in to comment.