Skip to content

Commit

Permalink
Prevent so type registration if schema declared directly (#177246)
Browse files Browse the repository at this point in the history
Fix #176668

## Summary

After a switch to model versions, saved object registrations are blocked
if any schema for a higher version is declared when not coupled with a
model version.


### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
TinaHeiligers and kibanamachine authored Feb 22, 2024
1 parent 6d44340 commit bf4b70c
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Side Public License, v 1.
*/

import { schema } from '@kbn/config-schema';
import { SavedObjectsType, SavedObjectsModelVersion } from '@kbn/core-saved-objects-server';
import { validateTypeMigrations } from './validate_migrations';

Expand Down Expand Up @@ -50,6 +51,9 @@ describe('validateTypeMigrations', () => {
bar: jest.fn(),
'1.2.3': jest.fn(),
},
schemas: {
'1.2.3': schema.object({ bar: schema.string() }),
},
});

expect(() => validate({ type })).toThrow(/Expected all properties to be semvers/i);
Expand Down Expand Up @@ -123,6 +127,23 @@ describe('validateTypeMigrations', () => {
`"Migration for type foo for version 8.10.0 registered after switchToModelVersionAt (8.9.0)"`
);
});

it('throws if a schema is specified for a version superior to switchToModelVersionAt', () => {
const type = createType({
name: 'foo',
switchToModelVersionAt: '8.9.0',
schemas: {
'8.10.0': schema.object({ name: schema.string() }),
},
});

expect(() =>
validate({ type, kibanaVersion: '8.10.0' })
).toThrowErrorMatchingInlineSnapshot(
`"Schema for type foo for version 8.10.0 registered after switchToModelVersionAt (8.9.0)"`
);
});

it('throws if a migration is specified for a version equal to switchToModelVersionAt', () => {
const type = createType({
name: 'foo',
Expand All @@ -139,6 +160,22 @@ describe('validateTypeMigrations', () => {
);
});

it('throws if a schema is specified for a version equal to switchToModelVersionAt', () => {
const type = createType({
name: 'foo',
switchToModelVersionAt: '8.9.0',
schemas: {
'8.9.0': schema.object({ name: schema.string() }),
},
});

expect(() =>
validate({ type, kibanaVersion: '8.10.0' })
).toThrowErrorMatchingInlineSnapshot(
`"Schema for type foo for version 8.9.0 registered after switchToModelVersionAt (8.9.0)"`
);
});

it('does not throw if a migration is specified for a version inferior to switchToModelVersionAt', () => {
const type = createType({
name: 'foo',
Expand All @@ -150,6 +187,18 @@ describe('validateTypeMigrations', () => {

expect(() => validate({ type, kibanaVersion: '8.10.0' })).not.toThrow();
});

it('does not throw if a schema is specified for a version inferior to switchToModelVersionAt', () => {
const type = createType({
name: 'foo',
switchToModelVersionAt: '8.9.0',
schemas: {
'8.7.0': schema.object({ name: schema.string() }),
},
});

expect(() => validate({ type, kibanaVersion: '8.10.0' })).not.toThrow();
});
});
});

Expand Down Expand Up @@ -245,6 +294,71 @@ describe('validateTypeMigrations', () => {
});
});

describe('modelVersions with schemas', () => {
const baseSchema = schema.object({ name: schema.string() }, { unknowns: 'ignore' });

it('throws if used without specifying switchToModelVersionAt', () => {
const type = createType({
name: 'foo',
modelVersions: {
1: {
changes: [],
schemas: {
forwardCompatibility: baseSchema,
create: baseSchema,
},
},
},
mappings: {
properties: {
name: { type: 'text' },
},
},
});

expect(() => validate({ type, kibanaVersion: '3.2.3' })).toThrowErrorMatchingInlineSnapshot(
`"Type foo: Using modelVersions requires to specify switchToModelVersionAt"`
);
});

it('does not throw passing a model version schema map', () => {
const someModelVersionWithSchema = {
changes: [],
schemas: {
forwardCompatibility: baseSchema.extends({}, { unknowns: 'ignore' }),
create: baseSchema,
},
};
const type = createType({
name: 'foo',
switchToModelVersionAt: '3.1.0',
modelVersions: {
'1': someModelVersionWithSchema,
},
mappings: {
properties: {
name: { type: 'text' },
},
},
});

expect(() => validate({ type, kibanaVersion: '3.2.3' })).not.toThrow();
});

it('does not throw passing an empty model version schema map', () => {
const someModelVersionWithSchema = { changes: [], schemas: {} };
const type = createType({
name: 'foo',
switchToModelVersionAt: '3.1.0',
modelVersions: {
'1': someModelVersionWithSchema,
},
});

expect(() => validate({ type, kibanaVersion: '3.2.3' })).not.toThrow();
});
});

describe('modelVersions mapping additions', () => {
it('throws when registering mapping additions not present in the global mappings', () => {
const type = createType({
Expand Down Expand Up @@ -274,7 +388,7 @@ describe('validateTypeMigrations', () => {
);
});

it('does not throw when registering mapping additions are present in the global mappings', () => {
it('does not throw when registering mapping additions are present in the global mappings with a schema', () => {
const type = createType({
name: 'foo',
switchToModelVersionAt: '8.8.0',
Expand All @@ -288,6 +402,7 @@ describe('validateTypeMigrations', () => {
},
},
],
schemas: {},
},
'2': {
changes: [
Expand Down Expand Up @@ -340,6 +455,30 @@ describe('validateTypeMigrations', () => {
`"Type foo: mappings added on model versions differs from the global mappings definition: field2.type"`
);
});

it('does not throw if a schema is specified for a modelVersion with no changes', () => {
const baseSchema = schema.object({ name: schema.string() });
const type = createType({
name: 'foo',
switchToModelVersionAt: '8.10.0',
modelVersions: {
1: {
changes: [],
schemas: {
forwardCompatibility: baseSchema.extends({}, { unknowns: 'ignore' }),
create: baseSchema,
},
},
},
mappings: {
properties: {
name: { type: 'text' },
},
},
});

expect(() => validate({ type, kibanaVersion: '3.2.3' })).not.toThrow();
});
});

describe('convertToMultiNamespaceTypeVersion', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,23 @@ export function validateTypeMigrations({
});
}

if (type.schemas) {
const schemaMap = typeof type.schemas === 'object' ? type.schemas : {};
assertObject(
schemaMap,
`Schemas map for type ${type.name} should be an object like { '2.0.0': {schema} }.`
);

Object.entries(schemaMap).forEach(([version, schema]) => {
assertValidSemver(kibanaVersion, version, type.name);
if (type.switchToModelVersionAt && Semver.gte(version, type.switchToModelVersionAt)) {
throw new Error(
`Schema for type ${type.name} for version ${version} registered after switchToModelVersionAt (${type.switchToModelVersionAt})`
);
}
});
}

if (type.modelVersions) {
const modelVersionMap =
typeof type.modelVersions === 'function' ? type.modelVersions() : type.modelVersions ?? {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
"cases-connector-mappings": "f9d1ac57e484e69506c36a8051e4d61f4a8cfd25",
"cases-telemetry": "f219eb7e26772884342487fc9602cfea07b3cedc",
"cases-user-actions": "483f10db9b3bd1617948d7032a98b7791bf87414",
"cloud-security-posture-settings": "675e47dd958fbce6c70a20baac12af3145e7c0ef",
"cloud-security-posture-settings": "e0f61c68bbb5e4cfa46ce8994fa001e417df51ca",
"config": "179b3e2bc672626aafce3cf92093a113f456af38",
"config-global": "8e8a134a2952df700d7d4ec51abb794bbd4cf6da",
"connector_token": "5a9ac29fe9c740eb114e9c40517245c71706b005",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,15 @@ export const cspSettings: SavedObjectsType = {
indexPattern: SECURITY_SOLUTION_SAVED_OBJECT_INDEX,
hidden: true,
namespaceType: 'agnostic',
schemas: {
'8.12.0': cspSettingsSchema,
modelVersions: {
1: {
changes: [],
schemas: {
forwardCompatibility: cspSettingsSchema.extends({}, { unknowns: 'ignore' }),
create: cspSettingsSchema,
},
},
},
schemas: {},
mappings: cspSettingsSavedObjectMapping,
};

0 comments on commit bf4b70c

Please sign in to comment.