Skip to content

Commit

Permalink
Allow SOC wrappers to be excluded (#40275)
Browse files Browse the repository at this point in the history
* allow SOC wrappers to be excluded

* remove example route

* add tests

* more testing
  • Loading branch information
legrego authored Jul 15, 2019
1 parent d5489fb commit 73a6b72
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,15 @@ test(`1, 1 throws Error`, () => {
priorityCollection.add(1, 1);
expect(() => priorityCollection.add(1, 1)).toThrowErrorMatchingSnapshot();
});

test(`#has when empty returns false`, () => {
const priorityCollection = new PriorityCollection();
expect(priorityCollection.has(() => true)).toEqual(false);
});

test(`#has returns result of predicate`, () => {
const priorityCollection = new PriorityCollection();
priorityCollection.add(1, 'foo');
expect(priorityCollection.has(val => val === 'foo')).toEqual(true);
expect(priorityCollection.has(val => val === 'bar')).toEqual(false);
});
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ export class PriorityCollection<T> {
this.array.splice(spliceIndex, 0, { priority, value });
}

public has(predicate: (value: T) => boolean): boolean {
return this.array.some(entry => predicate(entry.value));
}

public toPrioritizedArray(): T[] {
return this.array.map(entry => entry.value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,20 @@ test(`throws error when more than one scoped saved objects client factory is set
}).toThrowErrorMatchingSnapshot();
});

test(`throws error when registering a wrapper with a duplicate id`, () => {
const defaultClientFactoryMock = jest.fn();
const clientProvider = new ScopedSavedObjectsClientProvider({
defaultClientFactory: defaultClientFactoryMock,
});
const firstClientWrapperFactoryMock = jest.fn();
const secondClientWrapperFactoryMock = jest.fn();

clientProvider.addClientWrapperFactory(1, 'foo', secondClientWrapperFactoryMock);
expect(() =>
clientProvider.addClientWrapperFactory(0, 'foo', firstClientWrapperFactoryMock)
).toThrowErrorMatchingInlineSnapshot(`"wrapper factory with id foo is already defined"`);
});

test(`invokes and uses wrappers in specified order`, () => {
const defaultClient = Symbol();
const defaultClientFactoryMock = jest.fn().mockReturnValue(defaultClient);
Expand All @@ -76,8 +90,8 @@ test(`invokes and uses wrappers in specified order`, () => {
const secondClientWrapperFactoryMock = jest.fn().mockReturnValue(secondWrapperClient);
const request = Symbol();

clientProvider.addClientWrapperFactory(1, secondClientWrapperFactoryMock);
clientProvider.addClientWrapperFactory(0, firstClientWrapperFactoryMock);
clientProvider.addClientWrapperFactory(1, 'foo', secondClientWrapperFactoryMock);
clientProvider.addClientWrapperFactory(0, 'bar', firstClientWrapperFactoryMock);
const actualClient = clientProvider.getClient(request);

expect(actualClient).toBe(firstWrappedClient);
Expand All @@ -90,3 +104,54 @@ test(`invokes and uses wrappers in specified order`, () => {
client: defaultClient,
});
});

test(`does not invoke or use excluded wrappers`, () => {
const defaultClient = Symbol();
const defaultClientFactoryMock = jest.fn().mockReturnValue(defaultClient);
const clientProvider = new ScopedSavedObjectsClientProvider({
defaultClientFactory: defaultClientFactoryMock,
});
const firstWrappedClient = Symbol('first client');
const firstClientWrapperFactoryMock = jest.fn().mockReturnValue(firstWrappedClient);
const secondWrapperClient = Symbol('second client');
const secondClientWrapperFactoryMock = jest.fn().mockReturnValue(secondWrapperClient);
const request = Symbol();

clientProvider.addClientWrapperFactory(1, 'foo', secondClientWrapperFactoryMock);
clientProvider.addClientWrapperFactory(0, 'bar', firstClientWrapperFactoryMock);

const actualClient = clientProvider.getClient(request, {
excludedWrappers: ['foo'],
});

expect(actualClient).toBe(firstWrappedClient);
expect(firstClientWrapperFactoryMock).toHaveBeenCalledWith({
request,
client: defaultClient,
});
expect(secondClientWrapperFactoryMock).not.toHaveBeenCalled();
});

test(`allows all wrappers to be excluded`, () => {
const defaultClient = Symbol();
const defaultClientFactoryMock = jest.fn().mockReturnValue(defaultClient);
const clientProvider = new ScopedSavedObjectsClientProvider({
defaultClientFactory: defaultClientFactoryMock,
});
const firstWrappedClient = Symbol('first client');
const firstClientWrapperFactoryMock = jest.fn().mockReturnValue(firstWrappedClient);
const secondWrapperClient = Symbol('second client');
const secondClientWrapperFactoryMock = jest.fn().mockReturnValue(secondWrapperClient);
const request = Symbol();

clientProvider.addClientWrapperFactory(1, 'foo', secondClientWrapperFactoryMock);
clientProvider.addClientWrapperFactory(0, 'bar', firstClientWrapperFactoryMock);

const actualClient = clientProvider.getClient(request, {
excludedWrappers: ['foo', 'bar'],
});

expect(actualClient).toBe(defaultClient);
expect(firstClientWrapperFactoryMock).not.toHaveBeenCalled();
expect(secondClientWrapperFactoryMock).not.toHaveBeenCalled();
});
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,18 @@ export type SavedObjectsClientFactory<Request = unknown> = ({
request: Request;
}) => SavedObjectsClientContract;

export interface SavedObjectsClientProviderOptions {
excludedWrappers?: string[];
}

/**
* Provider for the Scoped Saved Object Client.
*/
export class ScopedSavedObjectsClientProvider<Request = unknown> {
private readonly _wrapperFactories = new PriorityCollection<
SavedObjectsClientWrapperFactory<Request>
>();
private readonly _wrapperFactories = new PriorityCollection<{
id: string;
factory: SavedObjectsClientWrapperFactory<Request>;
}>();
private _clientFactory: SavedObjectsClientFactory<Request>;
private readonly _originalClientFactory: SavedObjectsClientFactory<Request>;

Expand All @@ -54,9 +59,14 @@ export class ScopedSavedObjectsClientProvider<Request = unknown> {

addClientWrapperFactory(
priority: number,
wrapperFactory: SavedObjectsClientWrapperFactory<Request>
id: string,
factory: SavedObjectsClientWrapperFactory<Request>
): void {
this._wrapperFactories.add(priority, wrapperFactory);
if (this._wrapperFactories.has(entry => entry.id === id)) {
throw new Error(`wrapper factory with id ${id} is already defined`);
}

this._wrapperFactories.add(priority, { id, factory });
}

setClientFactory(customClientFactory: SavedObjectsClientFactory) {
Expand All @@ -67,15 +77,24 @@ export class ScopedSavedObjectsClientProvider<Request = unknown> {
this._clientFactory = customClientFactory;
}

getClient(request: Request): SavedObjectsClientContract {
getClient(
request: Request,
options: SavedObjectsClientProviderOptions = {}
): SavedObjectsClientContract {
const client = this._clientFactory({
request,
});

const excludedWrappers = options.excludedWrappers || [];

return this._wrapperFactories
.toPrioritizedArray()
.reduceRight((clientToWrap, wrapperFactory) => {
return wrapperFactory({
.reduceRight((clientToWrap, { id, factory }) => {
if (excludedWrappers.includes(id)) {
return clientToWrap;
}

return factory({
request,
client: clientToWrap,
});
Expand Down
4 changes: 2 additions & 2 deletions src/legacy/server/saved_objects/saved_objects_mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,14 +148,14 @@ export function savedObjectsMixin(kbnServer, server) {
server.decorate('server', 'savedObjects', service);

const savedObjectsClientCache = new WeakMap();
server.decorate('request', 'getSavedObjectsClient', function() {
server.decorate('request', 'getSavedObjectsClient', function(options) {
const request = this;

if (savedObjectsClientCache.has(request)) {
return savedObjectsClientCache.get(request);
}

const savedObjectsClient = server.savedObjects.getScopedSavedObjectsClient(request);
const savedObjectsClient = server.savedObjects.getScopedSavedObjectsClient(request, options);

savedObjectsClientCache.set(request, savedObjectsClient);
return savedObjectsClient;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export class Plugin {
// `namespace` is included into AAD.
core.savedObjects.addScopedSavedObjectsClientWrapperFactory(
Number.MAX_SAFE_INTEGER,
'encrypted_saved_objects',
({ client: baseClient }) => new EncryptedSavedObjectsClientWrapper({ baseClient, service })
);

Expand Down
2 changes: 1 addition & 1 deletion x-pack/legacy/plugins/security/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ export const security = (kibana) => new kibana.Plugin({
return new savedObjects.SavedObjectsClient(callWithRequestRepository);
});

savedObjects.addScopedSavedObjectsClientWrapperFactory(Number.MIN_SAFE_INTEGER, ({ client, request }) => {
savedObjects.addScopedSavedObjectsClientWrapperFactory(Number.MIN_SAFE_INTEGER, 'security', ({ client, request }) => {
if (authorization.mode.useRbacForRequest(request)) {
return new SecureSavedObjectsClientWrapper({
actions: authorization.actions,
Expand Down
1 change: 1 addition & 0 deletions x-pack/legacy/plugins/spaces/server/new_platform/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export class Plugin {
const { addScopedSavedObjectsClientWrapperFactory, types } = core.savedObjects;
addScopedSavedObjectsClientWrapperFactory(
Number.MAX_SAFE_INTEGER - 1,
'spaces',
spacesSavedObjectsClientWrapperFactory(spacesService, types)
);

Expand Down

0 comments on commit 73a6b72

Please sign in to comment.