From 8774c115e644dd1cd6932889379929055286df4a Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Thu, 21 Nov 2019 10:30:57 +0100 Subject: [PATCH 01/34] create service skeleton --- .../capabilities/capabilities_service.mock.ts | 39 +++++++++++++++++ .../capabilities/capabilities_service.ts | 43 +++++++++++++++++++ src/core/server/capabilities/index.ts | 20 +++++++++ src/core/server/index.ts | 11 ++++- src/core/server/internal_types.ts | 2 + src/core/server/legacy/legacy_service.test.ts | 2 + src/core/server/legacy/legacy_service.ts | 1 + src/core/server/mocks.ts | 3 ++ src/core/server/plugins/plugin_context.ts | 1 + src/core/server/server.ts | 6 +++ 10 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 src/core/server/capabilities/capabilities_service.mock.ts create mode 100644 src/core/server/capabilities/capabilities_service.ts create mode 100644 src/core/server/capabilities/index.ts diff --git a/src/core/server/capabilities/capabilities_service.mock.ts b/src/core/server/capabilities/capabilities_service.mock.ts new file mode 100644 index 0000000000000..f4c7272429285 --- /dev/null +++ b/src/core/server/capabilities/capabilities_service.mock.ts @@ -0,0 +1,39 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { CapabilitiesService, CapabilitiesSetup } from './capabilities_service'; + +const createSetupContractMock = () => { + const setupContract: jest.Mocked = {}; + return setupContract; +}; + +type CapabilitiesServiceContract = PublicMethodsOf; +const createMock = () => { + const mocked: jest.Mocked = { + setup: jest.fn().mockReturnValue(createSetupContractMock()), + start: jest.fn(), + }; + return mocked; +}; + +export const capabilitiesServiceMock = { + create: createMock, + createSetupContract: createSetupContractMock, +}; diff --git a/src/core/server/capabilities/capabilities_service.ts b/src/core/server/capabilities/capabilities_service.ts new file mode 100644 index 0000000000000..7224546dfb3fc --- /dev/null +++ b/src/core/server/capabilities/capabilities_service.ts @@ -0,0 +1,43 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { CoreContext } from '../core_context'; +import { Logger } from '..'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface CapabilitiesSetup {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface CapabilitiesStart {} + +export class CapabilitiesService { + private logger: Logger; + + constructor(core: CoreContext) { + this.logger = core.logger.get('capabilities-service'); + } + + public setup(): CapabilitiesSetup { + this.logger.debug('Setting up capabilities service'); + return {}; + } + + public start(): CapabilitiesStart { + return {}; + } +} diff --git a/src/core/server/capabilities/index.ts b/src/core/server/capabilities/index.ts new file mode 100644 index 0000000000000..4f67c2be8f517 --- /dev/null +++ b/src/core/server/capabilities/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { CapabilitiesService, CapabilitiesSetup, CapabilitiesStart } from './capabilities_service'; diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 31dec2c9b96ff..3e2fc7ae0f44f 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -45,6 +45,7 @@ import { PluginsServiceSetup, PluginsServiceStart, PluginOpaqueId } from './plug import { ContextSetup } from './context'; import { IUiSettingsClient, UiSettingsServiceSetup } from './ui_settings'; import { SavedObjectsClientContract } from './saved_objects/types'; +import { CapabilitiesSetup } from './capabilities'; export { bootstrap } from './bootstrap'; export { ConfigPath, ConfigService, EnvironmentMode, PackageInfo } from './config'; @@ -226,6 +227,8 @@ export interface RequestHandlerContext { * @public */ export interface CoreSetup { + /** {@link CapabilitiesSetup} */ + capabilities: CapabilitiesSetup; /** {@link ContextSetup} */ context: ContextSetup; /** {@link ElasticsearchServiceSetup} */ @@ -243,4 +246,10 @@ export interface CoreSetup { */ export interface CoreStart {} // eslint-disable-line @typescript-eslint/no-empty-interface -export { ContextSetup, PluginsServiceSetup, PluginsServiceStart, PluginOpaqueId }; +export { + CapabilitiesSetup, + ContextSetup, + PluginsServiceSetup, + PluginsServiceStart, + PluginOpaqueId, +}; diff --git a/src/core/server/internal_types.ts b/src/core/server/internal_types.ts index 1330c5aee64fd..6166ef5e82a66 100644 --- a/src/core/server/internal_types.ts +++ b/src/core/server/internal_types.ts @@ -22,9 +22,11 @@ import { InternalHttpServiceSetup } from './http'; import { InternalUiSettingsServiceSetup } from './ui_settings'; import { ContextSetup } from './context'; import { SavedObjectsServiceStart } from './saved_objects'; +import { CapabilitiesSetup } from './capabilities'; /** @internal */ export interface InternalCoreSetup { + capabilities: CapabilitiesSetup; context: ContextSetup; http: InternalHttpServiceSetup; elasticsearch: InternalElasticsearchServiceSetup; diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts index 030caa8324521..68bed8b60379f 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -47,6 +47,7 @@ import { KibanaMigrator } from '../saved_objects/migrations'; import { ISavedObjectsClientProvider } from '../saved_objects'; import { httpServiceMock } from '../http/http_service.mock'; import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock'; +import { capabilitiesServiceMock } from '../capabilities/capabilities_service.mock'; const MockKbnServer: jest.Mock = KbnServer as any; @@ -70,6 +71,7 @@ beforeEach(() => { setupDeps = { core: { + capabilities: capabilitiesServiceMock.createSetupContract(), context: contextServiceMock.createSetupContract(), elasticsearch: { legacy: {} } as any, uiSettings: uiSettingsServiceMock.createSetupContract(), diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 99963ad9ce3e8..699ae24e1f6dd 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -230,6 +230,7 @@ export class LegacyService implements CoreService { } ) { const coreSetup: CoreSetup = { + capabilities: setupDeps.core.capabilities, context: setupDeps.core.context, elasticsearch: { adminClient$: setupDeps.core.elasticsearch.adminClient$, diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index b51d5302e3274..37e91a594a14b 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -23,6 +23,7 @@ import { elasticsearchServiceMock } from './elasticsearch/elasticsearch_service. import { httpServiceMock } from './http/http_service.mock'; import { contextServiceMock } from './context/context_service.mock'; import { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; +import { capabilitiesServiceMock } from './capabilities/capabilities_service.mock'; export { httpServerMock } from './http/http_server.mocks'; export { sessionStorageMock } from './http/cookie_session_storage.mocks'; @@ -84,6 +85,7 @@ function createCoreSetupMock() { register: uiSettingsServiceMock.createSetupContract().register, }; const mock: MockedKeys = { + capabilities: capabilitiesServiceMock.createSetupContract(), context: contextServiceMock.createSetupContract(), elasticsearch: elasticsearchServiceMock.createSetupContract(), http: httpMock, @@ -101,6 +103,7 @@ function createCoreStartMock() { function createInternalCoreSetupMock() { const setupDeps = { + capabilities: capabilitiesServiceMock.createSetupContract(), context: contextServiceMock.createSetupContract(), elasticsearch: elasticsearchServiceMock.createSetupContract(), http: httpServiceMock.createSetupContract(), diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 9885a572ad8c0..d5d443851fa12 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -102,6 +102,7 @@ export function createPluginSetupContext( plugin: PluginWrapper ): CoreSetup { return { + capabilities: {}, context: { createContextContainer: deps.context.createContextContainer, }, diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 6c38de03f0f2d..331708939b140 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -41,11 +41,13 @@ import { ContextService } from './context'; import { SavedObjectsServiceSetup } from './saved_objects/saved_objects_service'; import { RequestHandlerContext } from '.'; import { InternalCoreSetup } from './internal_types'; +import { CapabilitiesService } from './capabilities'; const coreId = Symbol('core'); export class Server { public readonly configService: ConfigService; + private readonly capabilities: CapabilitiesService; private readonly context: ContextService; private readonly elasticsearch: ElasticsearchService; private readonly http: HttpService; @@ -71,6 +73,7 @@ export class Server { this.elasticsearch = new ElasticsearchService(core); this.savedObjects = new SavedObjectsService(core); this.uiSettings = new UiSettingsService(core); + this.capabilities = new CapabilitiesService(core); } public async setup() { @@ -95,6 +98,8 @@ export class Server { this.registerDefaultRoute(httpSetup); + const capabilitiesSetup = this.capabilities.setup(); + const elasticsearchServiceSetup = await this.elasticsearch.setup({ http: httpSetup, }); @@ -104,6 +109,7 @@ export class Server { }); const coreSetup: InternalCoreSetup = { + capabilities: capabilitiesSetup, context: contextServiceSetup, elasticsearch: elasticsearchServiceSetup, http: httpSetup, From 7b381213e6df71bc129d17a474f83d32e0aea32b Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Thu, 21 Nov 2019 12:50:12 +0100 Subject: [PATCH 02/34] move registerCapabilitiesModifier to capabilities service and rename to registerCapabilitiesSwitcher --- .../capabilities/capabilities_service.mock.ts | 4 +- .../capabilities/capabilities_service.ts | 13 +++-- src/core/server/capabilities/types.ts | 48 +++++++++++++++++++ src/core/server/plugins/plugin_context.ts | 4 +- .../capabilities/capabilities_mixin.test.ts | 11 ----- .../server/capabilities/capabilities_mixin.ts | 4 -- src/legacy/server/kbn_server.d.ts | 1 - x-pack/legacy/plugins/security/index.js | 1 - x-pack/legacy/plugins/spaces/index.ts | 3 -- x-pack/plugins/security/server/plugin.ts | 8 +--- x-pack/plugins/spaces/server/plugin.ts | 24 ++++------ .../api/__fixtures__/create_legacy_api.ts | 1 - 12 files changed, 76 insertions(+), 46 deletions(-) create mode 100644 src/core/server/capabilities/types.ts diff --git a/src/core/server/capabilities/capabilities_service.mock.ts b/src/core/server/capabilities/capabilities_service.mock.ts index f4c7272429285..b432b55597e2f 100644 --- a/src/core/server/capabilities/capabilities_service.mock.ts +++ b/src/core/server/capabilities/capabilities_service.mock.ts @@ -20,7 +20,9 @@ import { CapabilitiesService, CapabilitiesSetup } from './capabilities_service'; const createSetupContractMock = () => { - const setupContract: jest.Mocked = {}; + const setupContract: jest.Mocked = { + registerCapabilitiesSwitcher: jest.fn(), + }; return setupContract; }; diff --git a/src/core/server/capabilities/capabilities_service.ts b/src/core/server/capabilities/capabilities_service.ts index 7224546dfb3fc..e98436c905668 100644 --- a/src/core/server/capabilities/capabilities_service.ts +++ b/src/core/server/capabilities/capabilities_service.ts @@ -17,15 +17,18 @@ * under the License. */ +import { CapabilitiesSwitcher } from './types'; import { CoreContext } from '../core_context'; import { Logger } from '..'; -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface CapabilitiesSetup {} +export interface CapabilitiesSetup { + registerCapabilitiesSwitcher(switcher: CapabilitiesSwitcher): void; +} // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface CapabilitiesStart {} export class CapabilitiesService { + private capabilitiesSwitcher: CapabilitiesSwitcher[] = []; private logger: Logger; constructor(core: CoreContext) { @@ -34,7 +37,11 @@ export class CapabilitiesService { public setup(): CapabilitiesSetup { this.logger.debug('Setting up capabilities service'); - return {}; + return { + registerCapabilitiesSwitcher: (switcher: CapabilitiesSwitcher) => { + this.capabilitiesSwitcher.push(switcher); + }, + }; } public start(): CapabilitiesStart { diff --git a/src/core/server/capabilities/types.ts b/src/core/server/capabilities/types.ts new file mode 100644 index 0000000000000..1db0de010a1df --- /dev/null +++ b/src/core/server/capabilities/types.ts @@ -0,0 +1,48 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { KibanaRequest } from '../http'; + +/** + * The read-only set of capabilities available for the current UI session. + * Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, + * and the boolean is a flag indicating if the capability is enabled or disabled. + * + * @public + */ +export interface Capabilities { + /** Navigation link capabilities. */ + navLinks: Record; + + /** Management section capabilities. */ + management: { + [sectionId: string]: Record; + }; + + /** Catalogue capabilities. Catalogue entries drive the visibility of the Kibana homepage options. */ + catalogue: Record; + + /** Custom capabilities, registered by plugins. */ + [key: string]: Record>; +} + +export type CapabilitiesSwitcher = ( + request: KibanaRequest, + uiCapabilities: Capabilities +) => Capabilities | Promise; diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index d5d443851fa12..74a67b4d4bf9e 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -102,7 +102,9 @@ export function createPluginSetupContext( plugin: PluginWrapper ): CoreSetup { return { - capabilities: {}, + capabilities: { + registerCapabilitiesSwitcher: deps.capabilities.registerCapabilitiesSwitcher, + }, context: { createContextContainer: deps.context.createContextContainer, }, diff --git a/src/legacy/server/capabilities/capabilities_mixin.test.ts b/src/legacy/server/capabilities/capabilities_mixin.test.ts index 9b6827e1bb380..4d0003e85db46 100644 --- a/src/legacy/server/capabilities/capabilities_mixin.test.ts +++ b/src/legacy/server/capabilities/capabilities_mixin.test.ts @@ -75,17 +75,6 @@ describe('capabilitiesMixin', () => { ); }); - it('exposes server#registerCapabilitiesModifier for providing modifiers to the route', async () => { - const kbnServer = getKbnServer(); - await capabilitiesMixin(kbnServer, server); - const mockModifier1 = jest.fn(); - const mockModifier2 = jest.fn(); - server.registerCapabilitiesModifier(mockModifier1); - server.registerCapabilitiesModifier(mockModifier2); - - expect(mockRegisterCapabilitiesRoute.mock.calls[0][2]).toEqual([mockModifier1, mockModifier2]); - }); - it('exposes request#getCapabilities for retrieving legacy capabilities', async () => { const kbnServer = getKbnServer(); jest.spyOn(server, 'decorate'); diff --git a/src/legacy/server/capabilities/capabilities_mixin.ts b/src/legacy/server/capabilities/capabilities_mixin.ts index b41dfe42c40b2..da4e06712b2da 100644 --- a/src/legacy/server/capabilities/capabilities_mixin.ts +++ b/src/legacy/server/capabilities/capabilities_mixin.ts @@ -33,10 +33,6 @@ export type CapabilitiesModifier = ( export async function capabilitiesMixin(kbnServer: KbnServer, server: Server) { const modifiers: CapabilitiesModifier[] = []; - server.decorate('server', 'registerCapabilitiesModifier', (provider: CapabilitiesModifier) => { - modifiers.push(provider); - }); - // Some plugin capabilities are derived from data provided by other plugins, // so we need to wait until after all plugins have been init'd to fetch uiCapabilities. kbnServer.afterPluginsInit(async () => { diff --git a/src/legacy/server/kbn_server.d.ts b/src/legacy/server/kbn_server.d.ts index 9cc4e30d4252d..2f38ff8cb4125 100644 --- a/src/legacy/server/kbn_server.d.ts +++ b/src/legacy/server/kbn_server.d.ts @@ -70,7 +70,6 @@ declare module 'hapi' { usage: { collectorSet: any }; injectUiAppVars: (pluginName: string, getAppVars: () => { [key: string]: any }) => void; getHiddenUiAppById(appId: string): UiApp; - registerCapabilitiesModifier: (provider: CapabilitiesModifier) => void; addScopedTutorialContextFactory: ( scopedTutorialContextFactory: (...args: any[]) => any ) => void; diff --git a/x-pack/legacy/plugins/security/index.js b/x-pack/legacy/plugins/security/index.js index c098e3e67a6d9..d488880ed9aa9 100644 --- a/x-pack/legacy/plugins/security/index.js +++ b/x-pack/legacy/plugins/security/index.js @@ -121,7 +121,6 @@ export const security = (kibana) => new kibana.Plugin({ isSystemAPIRequest: server.plugins.kibana.systemApi.isSystemApiRequest.bind( server.plugins.kibana.systemApi ), - capabilities: { registerCapabilitiesModifier: server.registerCapabilitiesModifier }, cspRules: createCSPRuleString(config.get('csp.rules')), kibanaIndexName: config.get('kibana.index'), }); diff --git a/x-pack/legacy/plugins/spaces/index.ts b/x-pack/legacy/plugins/spaces/index.ts index 598d115a39e49..26aa60f63953d 100644 --- a/x-pack/legacy/plugins/spaces/index.ts +++ b/x-pack/legacy/plugins/spaces/index.ts @@ -130,9 +130,6 @@ export const spaces = (kibana: Record) => tutorial: { addScopedTutorialContextFactory: server.addScopedTutorialContextFactory, }, - capabilities: { - registerCapabilitiesModifier: server.registerCapabilitiesModifier, - }, auditLogger: { create: (pluginId: string) => new AuditLogger(server, pluginId, server.config(), server.plugins.xpack_main.info), diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 4b3997fe74f1b..da5eff8583a24 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -20,7 +20,6 @@ import { deepFreeze } from '../../../../src/core/utils'; import { SpacesPluginSetup } from '../../spaces/server'; import { PluginSetupContract as FeaturesSetupContract } from '../../features/server'; import { LicensingPluginSetup } from '../../licensing/server'; -import { CapabilitiesModifier } from '../../../../src/legacy/server/capabilities'; import { Authentication, setupAuthentication } from './authentication'; import { Authorization, setupAuthorization } from './authorization'; @@ -43,7 +42,6 @@ export type FeaturesService = Pick; */ export interface LegacyAPI { isSystemAPIRequest: (request: KibanaRequest) => boolean; - capabilities: { registerCapabilitiesModifier: (provider: CapabilitiesModifier) => void }; kibanaIndexName: string; cspRules: string; savedObjects: SavedObjectsLegacyService; @@ -153,6 +151,8 @@ export class Plugin { featuresService: features, }); + core.capabilities.registerCapabilitiesSwitcher(authz.disableUnauthorizedCapabilities); + defineRoutes({ router: core.http.createRouter(), basePath: core.http.basePath, @@ -192,10 +192,6 @@ export class Plugin { authz, legacyAPI, }); - - legacyAPI.capabilities.registerCapabilitiesModifier((request, capabilities) => - authz.disableUnauthorizedCapabilities(KibanaRequest.from(request), capabilities) - ); }, registerPrivilegesWithCluster: async () => await authz.registerPrivilegesWithCluster(), diff --git a/x-pack/plugins/spaces/server/plugin.ts b/x-pack/plugins/spaces/server/plugin.ts index 6511a5dc3f31b..66a1a3b3cfd36 100644 --- a/x-pack/plugins/spaces/server/plugin.ts +++ b/x-pack/plugins/spaces/server/plugin.ts @@ -6,11 +6,9 @@ import { Observable } from 'rxjs'; import { take } from 'rxjs/operators'; -import { CapabilitiesModifier } from 'src/legacy/server/capabilities'; import { SavedObjectsLegacyService, CoreSetup, - KibanaRequest, Logger, PluginInitializerContext, } from '../../../../src/core/server'; @@ -46,9 +44,6 @@ export interface LegacyAPI { tutorial: { addScopedTutorialContextFactory: (factory: any) => void; }; - capabilities: { - registerCapabilitiesModifier: (provider: CapabilitiesModifier) => void; - }; auditLogger: { create: (pluginId: string) => AuditLogger; }; @@ -135,6 +130,16 @@ export class Plugin { features: plugins.features, }); + core.capabilities.registerCapabilitiesSwitcher(async (request, uiCapabilities) => { + try { + const activeSpace = await spacesService.getActiveSpace(request); + const features = plugins.features.getFeatures(); + return toggleUICapabilities(features, uiCapabilities, activeSpace); + } catch (e) { + return uiCapabilities; + } + }); + if (plugins.security) { plugins.security.registerSpacesService(spacesService); } @@ -180,15 +185,6 @@ export class Plugin { legacyAPI.tutorial.addScopedTutorialContextFactory( createSpacesTutorialContextFactory(spacesService) ); - legacyAPI.capabilities.registerCapabilitiesModifier(async (request, uiCapabilities) => { - try { - const activeSpace = await spacesService.getActiveSpace(KibanaRequest.from(request)); - const features = featuresSetup.getFeatures(); - return toggleUICapabilities(features, uiCapabilities, activeSpace); - } catch (e) { - return uiCapabilities; - } - }); // Register a function with server to manage the collection of usage stats legacyAPI.usage.collectorSet.register( getSpacesUsageCollector({ diff --git a/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_legacy_api.ts b/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_legacy_api.ts index 38a973c1203d5..0d14b6c46fbcf 100644 --- a/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_legacy_api.ts +++ b/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_legacy_api.ts @@ -104,7 +104,6 @@ export const createLegacyAPI = ({ kibanaIndex: '', }, auditLogger: {} as any, - capabilities: {} as any, tutorial: {} as any, usage: {} as any, xpackMain: {} as any, From 590e7430a90a03030ba8d61a310625581dc370ec Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Thu, 21 Nov 2019 16:12:17 +0100 Subject: [PATCH 03/34] starts to move capabilities logic to CapabilitiesService --- .../capabilities/capabilities_service.mock.ts | 1 + .../capabilities/capabilities_service.ts | 38 ++++++++-- .../capabilities/merge_capabilities.test.ts | 76 +++++++++++++++++++ .../server/capabilities/merge_capabilities.ts | 35 +++++++++ .../capabilities/resolve_capabilities.test.ts | 70 +++++++++++++++++ .../capabilities/resolve_capabilities.ts | 30 ++++++++ src/core/server/capabilities/types.ts | 2 + src/core/server/plugins/plugin_context.ts | 1 + 8 files changed, 246 insertions(+), 7 deletions(-) create mode 100644 src/core/server/capabilities/merge_capabilities.test.ts create mode 100644 src/core/server/capabilities/merge_capabilities.ts create mode 100644 src/core/server/capabilities/resolve_capabilities.test.ts create mode 100644 src/core/server/capabilities/resolve_capabilities.ts diff --git a/src/core/server/capabilities/capabilities_service.mock.ts b/src/core/server/capabilities/capabilities_service.mock.ts index b432b55597e2f..c7a4bb7444356 100644 --- a/src/core/server/capabilities/capabilities_service.mock.ts +++ b/src/core/server/capabilities/capabilities_service.mock.ts @@ -21,6 +21,7 @@ import { CapabilitiesService, CapabilitiesSetup } from './capabilities_service'; const createSetupContractMock = () => { const setupContract: jest.Mocked = { + registerCapabilitiesProvider: jest.fn(), registerCapabilitiesSwitcher: jest.fn(), }; return setupContract; diff --git a/src/core/server/capabilities/capabilities_service.ts b/src/core/server/capabilities/capabilities_service.ts index e98436c905668..2577747bca3e9 100644 --- a/src/core/server/capabilities/capabilities_service.ts +++ b/src/core/server/capabilities/capabilities_service.ts @@ -17,18 +17,31 @@ * under the License. */ -import { CapabilitiesSwitcher } from './types'; +import { Capabilities, CapabilitiesProvider, CapabilitiesSwitcher } from './types'; import { CoreContext } from '../core_context'; -import { Logger } from '..'; +import { Logger } from '../logging'; +import { KibanaRequest } from '../http'; +import { mergeCapabilities } from './merge_capabilities'; +import { capabilitiesResolver } from './resolve_capabilities'; export interface CapabilitiesSetup { + registerCapabilitiesProvider(provider: CapabilitiesProvider): void; registerCapabilitiesSwitcher(switcher: CapabilitiesSwitcher): void; } -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface CapabilitiesStart {} + +export interface CapabilitiesStart { + resolveCapabilities(request: KibanaRequest): Promise; +} + +const defaultCapabilities: Capabilities = { + navLinks: {}, + management: {}, + catalogue: {}, +}; export class CapabilitiesService { - private capabilitiesSwitcher: CapabilitiesSwitcher[] = []; + private capabilitiesProviders: CapabilitiesProvider[] = []; + private capabilitiesSwitchers: CapabilitiesSwitcher[] = []; private logger: Logger; constructor(core: CoreContext) { @@ -38,13 +51,24 @@ export class CapabilitiesService { public setup(): CapabilitiesSetup { this.logger.debug('Setting up capabilities service'); return { + registerCapabilitiesProvider: (provider: CapabilitiesProvider) => { + this.capabilitiesProviders.push(provider); + }, registerCapabilitiesSwitcher: (switcher: CapabilitiesSwitcher) => { - this.capabilitiesSwitcher.push(switcher); + this.capabilitiesSwitchers.push(switcher); }, }; } public start(): CapabilitiesStart { - return {}; + return { + resolveCapabilities: (request: KibanaRequest) => { + const capabilities = mergeCapabilities( + defaultCapabilities, + ...this.capabilitiesProviders.map(provider => provider()) + ); + return capabilitiesResolver(capabilities, this.capabilitiesSwitchers)(request); + }, + }; } } diff --git a/src/core/server/capabilities/merge_capabilities.test.ts b/src/core/server/capabilities/merge_capabilities.test.ts new file mode 100644 index 0000000000000..ca30dc4bf2f81 --- /dev/null +++ b/src/core/server/capabilities/merge_capabilities.test.ts @@ -0,0 +1,76 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { mergeCapabilities } from './merge_capabilities'; + +describe('mergeCapabilities', () => { + it('merges empty object with non-empty object', () => { + const capabilities = mergeCapabilities({ foo: {} }, { foo: { bar: true } }); + expect(capabilities).toEqual({ foo: { bar: true } }); + }); + + it('merges nested object properties', () => { + const capabilities = mergeCapabilities({ foo: { baz: true } }, { foo: { bar: true } }); + expect(capabilities).toEqual({ foo: { bar: true, baz: true } }); + }); + + it('merges all object properties', () => { + const capabilities = mergeCapabilities({ foo: { bar: true } }, { hello: { dolly: true } }); + expect(capabilities).toEqual({ foo: { bar: true }, hello: { dolly: true } }); + }); + + it('merges boolean as same path if they are equals', () => { + const capabilities = mergeCapabilities( + { foo: { bar: true, dolly: false, a: true } }, + { foo: { bar: true, dolly: false, b: false } } + ); + expect(capabilities).toEqual({ foo: { bar: true, dolly: false, a: true, b: false } }); + }); + + it('throws if boolean at same path are not equals', () => { + expect(() => { + mergeCapabilities({ foo: { bar: false } }, { foo: { bar: true } }); + }).toThrowErrorMatchingInlineSnapshot( + `"conflict trying to merge booleans with different values"` + ); + + expect(() => { + mergeCapabilities({ foo: { bar: true } }, { foo: { bar: false } }); + }).toThrowErrorMatchingInlineSnapshot( + `"conflict trying to merge booleans with different values"` + ); + }); + + it('throws if value as same path is boolean on left and object on right', () => { + expect(() => { + mergeCapabilities({ foo: { bar: false } }, { foo: { bar: {} } }); + }).toThrowErrorMatchingInlineSnapshot(`"conflict trying to merge boolean with object"`); + expect(() => { + mergeCapabilities({ foo: { bar: false } }, { foo: { bar: { baz: false } } }); + }).toThrowErrorMatchingInlineSnapshot(`"conflict trying to merge boolean with object"`); + }); + + it('should not alter the input capabilities', () => { + const left = { foo: { bar: true } }; + const right = { hello: { dolly: true } }; + mergeCapabilities(left, right); + expect(left).toEqual({ foo: { bar: true } }); + expect(right).toEqual({ hello: { dolly: true } }); + }); +}); diff --git a/src/core/server/capabilities/merge_capabilities.ts b/src/core/server/capabilities/merge_capabilities.ts new file mode 100644 index 0000000000000..95296346ad835 --- /dev/null +++ b/src/core/server/capabilities/merge_capabilities.ts @@ -0,0 +1,35 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { merge } from 'lodash'; +import { Capabilities } from './types'; + +export const mergeCapabilities = (...sources: Array>): Capabilities => + merge({}, ...sources, (a: any, b: any) => { + if ( + (typeof a === 'boolean' && typeof b === 'object') || + (typeof a === 'object' && typeof b === 'boolean') + ) { + throw new Error(`conflict trying to merge boolean with object`); + } + + if (typeof a === 'boolean' && typeof b === 'boolean' && a !== b) { + throw new Error(`conflict trying to merge booleans with different values`); + } + }); diff --git a/src/core/server/capabilities/resolve_capabilities.test.ts b/src/core/server/capabilities/resolve_capabilities.test.ts new file mode 100644 index 0000000000000..8d71dc350fae2 --- /dev/null +++ b/src/core/server/capabilities/resolve_capabilities.test.ts @@ -0,0 +1,70 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Capabilities } from './types'; +import { capabilitiesResolver } from './resolve_capabilities'; +import { KibanaRequest } from '../http'; +import { httpServerMock } from '../http/http_server.mocks'; + +describe('capabilitiesResolver', () => { + let defaultCaps: Capabilities; + let request: KibanaRequest; + + beforeEach(() => { + defaultCaps = { + navLinks: {}, + catalogue: {}, + management: {}, + }; + request = httpServerMock.createKibanaRequest(); + }); + + it('should returns the initial capabilities if no switcher are used', async () => { + const result = await capabilitiesResolver(defaultCaps, [])(request); + expect(result).toEqual(defaultCaps); + }); + + it('should apply the switcher to the capabilities ', async () => { + const caps = { + ...defaultCaps, + navLinks: { + A: true, + B: true, + }, + }; + const switcher = (req: KibanaRequest, capabilities: Capabilities) => ({ + ...capabilities, + navLinks: { + ...capabilities.navLinks, + A: false, + }, + }); + const result = await capabilitiesResolver(caps, [switcher])(request); + expect(result).toMatchInlineSnapshot(` + Object { + "catalogue": Object {}, + "management": Object {}, + "navLinks": Object { + "A": false, + "B": true, + }, + } + `); + }); +}); diff --git a/src/core/server/capabilities/resolve_capabilities.ts b/src/core/server/capabilities/resolve_capabilities.ts new file mode 100644 index 0000000000000..bfd2ae344fdd1 --- /dev/null +++ b/src/core/server/capabilities/resolve_capabilities.ts @@ -0,0 +1,30 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Capabilities, CapabilitiesSwitcher } from './types'; +import { KibanaRequest } from '../http'; + +export const capabilitiesResolver = ( + capabilities: Capabilities, + switchers: CapabilitiesSwitcher[] +) => async (request: KibanaRequest): Promise => { + return switchers.reduce(async (caps, switcher) => { + return switcher(request, await caps); + }, Promise.resolve(capabilities)); +}; diff --git a/src/core/server/capabilities/types.ts b/src/core/server/capabilities/types.ts index 1db0de010a1df..0cf686f7ebb26 100644 --- a/src/core/server/capabilities/types.ts +++ b/src/core/server/capabilities/types.ts @@ -42,6 +42,8 @@ export interface Capabilities { [key: string]: Record>; } +export type CapabilitiesProvider = () => Partial; + export type CapabilitiesSwitcher = ( request: KibanaRequest, uiCapabilities: Capabilities diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 74a67b4d4bf9e..a4ede8b9f1a14 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -103,6 +103,7 @@ export function createPluginSetupContext( ): CoreSetup { return { capabilities: { + registerCapabilitiesProvider: deps.capabilities.registerCapabilitiesProvider, registerCapabilitiesSwitcher: deps.capabilities.registerCapabilitiesSwitcher, }, context: { From a0628bbf21b3da282f3d8dba4502c1576c738abb Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Fri, 22 Nov 2019 08:51:55 +0100 Subject: [PATCH 04/34] move capabilities route to service --- .../capabilities/capabilities_service.ts | 53 +++++-- .../capabilities/resolve_capabilities.test.ts | 8 +- .../capabilities/resolve_capabilities.ts | 16 +- src/core/server/server.ts | 2 +- .../capabilities_mixin.test.mocks.ts | 23 --- .../capabilities/capabilities_mixin.test.ts | 40 ----- .../server/capabilities/capabilities_mixin.ts | 3 - .../capabilities/capabilities_route.test.ts | 137 ------------------ .../server/capabilities/capabilities_route.ts | 54 ------- 9 files changed, 58 insertions(+), 278 deletions(-) delete mode 100644 src/legacy/server/capabilities/capabilities_mixin.test.mocks.ts delete mode 100644 src/legacy/server/capabilities/capabilities_route.test.ts delete mode 100644 src/legacy/server/capabilities/capabilities_route.ts diff --git a/src/core/server/capabilities/capabilities_service.ts b/src/core/server/capabilities/capabilities_service.ts index 2577747bca3e9..1af3aeb215af4 100644 --- a/src/core/server/capabilities/capabilities_service.ts +++ b/src/core/server/capabilities/capabilities_service.ts @@ -20,9 +20,9 @@ import { Capabilities, CapabilitiesProvider, CapabilitiesSwitcher } from './types'; import { CoreContext } from '../core_context'; import { Logger } from '../logging'; -import { KibanaRequest } from '../http'; +import { InternalHttpServiceSetup, KibanaRequest } from '../http'; import { mergeCapabilities } from './merge_capabilities'; -import { capabilitiesResolver } from './resolve_capabilities'; +import { getCapabilitiesResolver, CapabilitiesResolver } from './resolve_capabilities'; export interface CapabilitiesSetup { registerCapabilitiesProvider(provider: CapabilitiesProvider): void; @@ -33,6 +33,10 @@ export interface CapabilitiesStart { resolveCapabilities(request: KibanaRequest): Promise; } +interface SetupDeps { + http: InternalHttpServiceSetup; +} + const defaultCapabilities: Capabilities = { navLinks: {}, management: {}, @@ -40,16 +44,27 @@ const defaultCapabilities: Capabilities = { }; export class CapabilitiesService { - private capabilitiesProviders: CapabilitiesProvider[] = []; - private capabilitiesSwitchers: CapabilitiesSwitcher[] = []; - private logger: Logger; + private readonly logger: Logger; + private readonly capabilitiesProviders: CapabilitiesProvider[] = []; + private readonly capabilitiesSwitchers: CapabilitiesSwitcher[] = []; + private readonly resolver: CapabilitiesResolver; constructor(core: CoreContext) { this.logger = core.logger.get('capabilities-service'); + this.resolver = getCapabilitiesResolver( + () => + mergeCapabilities( + defaultCapabilities, + ...this.capabilitiesProviders.map(provider => provider()) + ), + () => this.capabilitiesSwitchers + ); } - public setup(): CapabilitiesSetup { + public setup(setupDeps: SetupDeps): CapabilitiesSetup { this.logger.debug('Setting up capabilities service'); + this.setupCapabilitiesRoute(setupDeps.http); + return { registerCapabilitiesProvider: (provider: CapabilitiesProvider) => { this.capabilitiesProviders.push(provider); @@ -62,13 +77,25 @@ export class CapabilitiesService { public start(): CapabilitiesStart { return { - resolveCapabilities: (request: KibanaRequest) => { - const capabilities = mergeCapabilities( - defaultCapabilities, - ...this.capabilitiesProviders.map(provider => provider()) - ); - return capabilitiesResolver(capabilities, this.capabilitiesSwitchers)(request); - }, + resolveCapabilities: request => this.resolver(request), }; } + + private setupCapabilitiesRoute(http: InternalHttpServiceSetup) { + const router = http.createRouter('/core/capabilities'); + router.post( + { + path: '/', + validate: false, + }, + async (ctx, req, res) => { + const capabilities = await this.resolver(req); + return res.ok({ + body: { + capabilities, + }, + }); + } + ); + } } diff --git a/src/core/server/capabilities/resolve_capabilities.test.ts b/src/core/server/capabilities/resolve_capabilities.test.ts index 8d71dc350fae2..eee8182a1ba99 100644 --- a/src/core/server/capabilities/resolve_capabilities.test.ts +++ b/src/core/server/capabilities/resolve_capabilities.test.ts @@ -18,11 +18,11 @@ */ import { Capabilities } from './types'; -import { capabilitiesResolver } from './resolve_capabilities'; +import { resolveCapabilities } from './resolve_capabilities'; import { KibanaRequest } from '../http'; import { httpServerMock } from '../http/http_server.mocks'; -describe('capabilitiesResolver', () => { +describe('resolveCapabilities', () => { let defaultCaps: Capabilities; let request: KibanaRequest; @@ -36,7 +36,7 @@ describe('capabilitiesResolver', () => { }); it('should returns the initial capabilities if no switcher are used', async () => { - const result = await capabilitiesResolver(defaultCaps, [])(request); + const result = await resolveCapabilities(defaultCaps, [], request); expect(result).toEqual(defaultCaps); }); @@ -55,7 +55,7 @@ describe('capabilitiesResolver', () => { A: false, }, }); - const result = await capabilitiesResolver(caps, [switcher])(request); + const result = await resolveCapabilities(caps, [switcher], request); expect(result).toMatchInlineSnapshot(` Object { "catalogue": Object {}, diff --git a/src/core/server/capabilities/resolve_capabilities.ts b/src/core/server/capabilities/resolve_capabilities.ts index bfd2ae344fdd1..a755d074f67ac 100644 --- a/src/core/server/capabilities/resolve_capabilities.ts +++ b/src/core/server/capabilities/resolve_capabilities.ts @@ -20,10 +20,20 @@ import { Capabilities, CapabilitiesSwitcher } from './types'; import { KibanaRequest } from '../http'; -export const capabilitiesResolver = ( +export type CapabilitiesResolver = (request: KibanaRequest) => Promise; + +export const getCapabilitiesResolver = ( + capabilities: () => Capabilities, + switchers: () => CapabilitiesSwitcher[] +): CapabilitiesResolver => async (request: KibanaRequest): Promise => { + return resolveCapabilities(capabilities(), switchers(), request); +}; + +export const resolveCapabilities = async ( capabilities: Capabilities, - switchers: CapabilitiesSwitcher[] -) => async (request: KibanaRequest): Promise => { + switchers: CapabilitiesSwitcher[], + request: KibanaRequest +): Promise => { return switchers.reduce(async (caps, switcher) => { return switcher(request, await caps); }, Promise.resolve(capabilities)); diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 331708939b140..594f216bd9521 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -98,7 +98,7 @@ export class Server { this.registerDefaultRoute(httpSetup); - const capabilitiesSetup = this.capabilities.setup(); + const capabilitiesSetup = this.capabilities.setup({ http: httpSetup }); const elasticsearchServiceSetup = await this.elasticsearch.setup({ http: httpSetup, diff --git a/src/legacy/server/capabilities/capabilities_mixin.test.mocks.ts b/src/legacy/server/capabilities/capabilities_mixin.test.mocks.ts deleted file mode 100644 index 64300604b3e9d..0000000000000 --- a/src/legacy/server/capabilities/capabilities_mixin.test.mocks.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export const mockRegisterCapabilitiesRoute = jest.fn(); -jest.mock('./capabilities_route', () => ({ - registerCapabilitiesRoute: mockRegisterCapabilitiesRoute, -})); diff --git a/src/legacy/server/capabilities/capabilities_mixin.test.ts b/src/legacy/server/capabilities/capabilities_mixin.test.ts index 4d0003e85db46..bf78a31bbf5da 100644 --- a/src/legacy/server/capabilities/capabilities_mixin.test.ts +++ b/src/legacy/server/capabilities/capabilities_mixin.test.ts @@ -19,7 +19,6 @@ import { Server } from 'hapi'; import KbnServer from '../kbn_server'; -import { mockRegisterCapabilitiesRoute } from './capabilities_mixin.test.mocks'; import { capabilitiesMixin } from './capabilities_mixin'; @@ -36,45 +35,6 @@ describe('capabilitiesMixin', () => { server = new Server(); }); - afterEach(() => { - mockRegisterCapabilitiesRoute.mockClear(); - }); - - it('calls registerCapabilitiesRoute with merged uiCapabilitiesProviers', async () => { - const kbnServer = getKbnServer([ - { - getUiCapabilitiesProvider: () => () => ({ - app1: { read: true }, - management: { section1: { feature1: true } }, - }), - }, - { - getUiCapabilitiesProvider: () => () => ({ - app2: { write: true }, - catalogue: { feature3: true }, - management: { section2: { feature2: true } }, - }), - }, - ]); - - await capabilitiesMixin(kbnServer, server); - - expect(mockRegisterCapabilitiesRoute).toHaveBeenCalledWith( - server, - { - app1: { read: true }, - app2: { write: true }, - catalogue: { feature3: true }, - management: { - section1: { feature1: true }, - section2: { feature2: true }, - }, - navLinks: {}, - }, - [] - ); - }); - it('exposes request#getCapabilities for retrieving legacy capabilities', async () => { const kbnServer = getKbnServer(); jest.spyOn(server, 'decorate'); diff --git a/src/legacy/server/capabilities/capabilities_mixin.ts b/src/legacy/server/capabilities/capabilities_mixin.ts index da4e06712b2da..12708f17fbf72 100644 --- a/src/legacy/server/capabilities/capabilities_mixin.ts +++ b/src/legacy/server/capabilities/capabilities_mixin.ts @@ -21,7 +21,6 @@ import { Server, Request } from 'hapi'; import { Capabilities } from '../../../core/public'; import KbnServer from '../kbn_server'; -import { registerCapabilitiesRoute } from './capabilities_route'; import { mergeCapabilities } from './merge_capabilities'; import { resolveCapabilities } from './resolve_capabilities'; @@ -57,7 +56,5 @@ export async function capabilitiesMixin(kbnServer: KbnServer, server: Server) { return resolveCapabilities(this, modifiers, defaultCapabilities, { navLinks }); }); - - registerCapabilitiesRoute(server, defaultCapabilities, modifiers); }); } diff --git a/src/legacy/server/capabilities/capabilities_route.test.ts b/src/legacy/server/capabilities/capabilities_route.test.ts deleted file mode 100644 index 6f3f706379642..0000000000000 --- a/src/legacy/server/capabilities/capabilities_route.test.ts +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Server } from 'hapi'; -import { registerCapabilitiesRoute } from './capabilities_route'; -import { Capabilities } from '../../../core/public'; - -describe('capabilities api', () => { - const defaultCapabilities = { - catalogue: { - feature1: true, - feature2: true, - }, - management: { - section1: { - read: true, - }, - section2: { - write: true, - }, - }, - navLinks: { - app1: true, - app2: true, - }, - myApp: { - read: true, - write: true, - kioskMode: true, - }, - } as Capabilities; - - let server: Server; - - beforeEach(() => { - server = new Server(); - }); - - it('returns unmodified uiCapabilities if no modifiers are available', async () => { - registerCapabilitiesRoute(server, defaultCapabilities, []); - const resp = await server.inject({ - method: 'POST', - url: '/api/capabilities', - payload: { capabilities: {} }, - }); - expect(JSON.parse(resp.payload)).toEqual({ - capabilities: defaultCapabilities, - }); - }); - - it('merges payload capabilities with defaultCapabilities', async () => { - registerCapabilitiesRoute(server, defaultCapabilities, []); - const resp = await server.inject({ - method: 'POST', - url: '/api/capabilities', - payload: { capabilities: { navLinks: { app3: true } } }, - }); - expect(JSON.parse(resp.payload)).toEqual({ - capabilities: { - ...defaultCapabilities, - navLinks: { - ...defaultCapabilities.navLinks, - app3: true, - }, - }, - }); - }); - - it('allows a single provider to modify uiCapabilities', async () => { - registerCapabilitiesRoute(server, defaultCapabilities, [ - (req, caps) => { - caps.management.section2.write = false; - caps.myApp.write = false; - return caps; - }, - ]); - const resp = await server.inject({ - method: 'POST', - url: '/api/capabilities', - payload: { capabilities: {} }, - }); - const results = JSON.parse(resp.payload); - expect(results.capabilities.management.section2.write).toBe(false); - expect(results.capabilities.myApp.write).toBe(false); - }); - - it('allows multiple providers to modify uiCapabilities', async () => { - registerCapabilitiesRoute(server, defaultCapabilities, [ - (req, caps) => { - caps.management.section2.write = false; - return caps; - }, - (req, caps) => { - caps.myApp.write = false; - return caps; - }, - ]); - const resp = await server.inject({ - method: 'POST', - url: '/api/capabilities', - payload: { capabilities: {} }, - }); - const results = JSON.parse(resp.payload); - expect(results.capabilities.management.section2.write).toBe(false); - expect(results.capabilities.myApp.write).toBe(false); - }); - - it('returns an error if any providers fail', async () => { - registerCapabilitiesRoute(server, defaultCapabilities, [ - (req, caps) => { - throw new Error(`Couldn't fetch license`); - }, - ]); - const resp = await server.inject({ - method: 'POST', - url: '/api/capabilities', - payload: { capabilities: {} }, - }); - expect(resp.statusCode).toBe(500); - }); -}); diff --git a/src/legacy/server/capabilities/capabilities_route.ts b/src/legacy/server/capabilities/capabilities_route.ts deleted file mode 100644 index 5564fbb295a62..0000000000000 --- a/src/legacy/server/capabilities/capabilities_route.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import Joi from 'joi'; -import { Server } from 'hapi'; - -import { Capabilities } from '../../../core/public'; -import { CapabilitiesModifier } from './capabilities_mixin'; -import { resolveCapabilities } from './resolve_capabilities'; - -export const registerCapabilitiesRoute = ( - server: Server, - defaultCapabilities: Capabilities, - modifiers: CapabilitiesModifier[] -) => { - server.route({ - path: '/api/capabilities', - method: 'POST', - options: { - validate: { - payload: Joi.object({ - capabilities: Joi.object().required(), - }).required(), - }, - }, - async handler(request) { - const { capabilities } = request.payload as { capabilities: Capabilities }; - return { - capabilities: await resolveCapabilities( - request, - modifiers, - defaultCapabilities, - capabilities - ), - }; - }, - }); -}; From 5182e149e36f534b8b65c18761ffd25f206908b9 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Fri, 22 Nov 2019 09:23:57 +0100 Subject: [PATCH 05/34] add initial integration test for capabilities route --- .../capabilities/capabilities_service.ts | 2 +- src/core/server/capabilities/index.ts | 1 + .../capabilities_service.test.ts | 132 ++++++++++++++++++ 3 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 src/core/server/capabilities/integration_tests/capabilities_service.test.ts diff --git a/src/core/server/capabilities/capabilities_service.ts b/src/core/server/capabilities/capabilities_service.ts index 1af3aeb215af4..533ab8e8a40c8 100644 --- a/src/core/server/capabilities/capabilities_service.ts +++ b/src/core/server/capabilities/capabilities_service.ts @@ -85,7 +85,7 @@ export class CapabilitiesService { const router = http.createRouter('/core/capabilities'); router.post( { - path: '/', + path: '', validate: false, }, async (ctx, req, res) => { diff --git a/src/core/server/capabilities/index.ts b/src/core/server/capabilities/index.ts index 4f67c2be8f517..ac9454f01391c 100644 --- a/src/core/server/capabilities/index.ts +++ b/src/core/server/capabilities/index.ts @@ -18,3 +18,4 @@ */ export { CapabilitiesService, CapabilitiesSetup, CapabilitiesStart } from './capabilities_service'; +export { Capabilities, CapabilitiesSwitcher, CapabilitiesProvider } from './types'; diff --git a/src/core/server/capabilities/integration_tests/capabilities_service.test.ts b/src/core/server/capabilities/integration_tests/capabilities_service.test.ts new file mode 100644 index 0000000000000..c921bb4082c2e --- /dev/null +++ b/src/core/server/capabilities/integration_tests/capabilities_service.test.ts @@ -0,0 +1,132 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { BehaviorSubject } from 'rxjs'; +import { ByteSizeValue } from '@kbn/config-schema'; +import supertest from 'supertest'; + +import { HttpService, InternalHttpServiceSetup } from '../../http'; +import { configServiceMock } from '../../config/config_service.mock'; +import { contextServiceMock } from '../../context/context_service.mock'; +import { loggingServiceMock } from '../../logging/logging_service.mock'; +import { Env } from '../../config'; +import { getEnvOptions } from '../../config/__mocks__/env'; +import { CapabilitiesService, CapabilitiesSetup } from '..'; + +const coreId = Symbol('core'); +const env = Env.createDefault(getEnvOptions()); + +const createHttpServer = (): HttpService => { + const logger = loggingServiceMock.create(); + + const configService = configServiceMock.create(); + configService.atPath.mockReturnValue( + new BehaviorSubject({ + hosts: ['localhost'], + maxPayload: new ByteSizeValue(1024), + autoListen: true, + ssl: { + enabled: false, + }, + } as any) + ); + const coreContext = { + coreId, + env, + logger, + configService: configService as any, + }; + return new HttpService(coreContext); +}; + +describe('CapabilitiesService', () => { + let server: HttpService; + let httpSetup: InternalHttpServiceSetup; + + let service: CapabilitiesService; + let serviceSetup: CapabilitiesSetup; + + beforeEach(async () => { + server = createHttpServer(); + httpSetup = await server.setup({ + context: contextServiceMock.createSetupContract(), + }); + service = new CapabilitiesService({ + coreId, + env, + logger: loggingServiceMock.create(), + configService: {} as any, + }); + serviceSetup = await service.setup({ http: httpSetup }); + await server.start(); + }); + + afterEach(async () => { + await server.stop(); + }); + + describe('/core/capabilities route', () => { + it('is exposed', async () => { + const result = await supertest(httpSetup.server.listener) + .post('/core/capabilities') + .send({}) + .expect(200); + expect(result.body).toMatchInlineSnapshot(` + Object { + "capabilities": Object { + "catalogue": Object {}, + "management": Object {}, + "navLinks": Object {}, + }, + } + `); + }); + + it('uses the service capabilities providers', async () => { + serviceSetup.registerCapabilitiesProvider(() => ({ + navLinks: { + app: true, + otherApp: true, + }, + catalogue: { + something: true, + }, + })); + + const result = await supertest(httpSetup.server.listener) + .post('/core/capabilities') + .send({}) + .expect(200); + expect(result.body).toMatchInlineSnapshot(` + Object { + "capabilities": Object { + "catalogue": Object { + "something": true, + }, + "management": Object {}, + "navLinks": Object { + "app": true, + "otherApp": true, + }, + }, + } + `); + }); + }); +}); From b700cdaca4e3f384048385a0317b0d8d510197d9 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Fri, 22 Nov 2019 13:04:54 +0100 Subject: [PATCH 06/34] capabilitiesMixin now delegates to capability service --- .../capabilities/capabilities_service.mock.ts | 12 ++- src/core/server/index.ts | 8 +- src/core/server/internal_types.ts | 3 +- src/core/server/legacy/legacy_service.test.ts | 1 + src/core/server/legacy/legacy_service.ts | 4 +- src/core/server/mocks.ts | 4 +- src/core/server/plugins/plugin_context.ts | 6 +- src/core/server/plugins/plugins_service.ts | 6 +- .../server/plugins/plugins_system.test.ts | 9 +- src/core/server/server.ts | 6 +- .../capabilities/capabilities_mixin.test.ts | 46 +++++++++- .../server/capabilities/capabilities_mixin.ts | 64 +++++++------- src/legacy/server/capabilities/index.ts | 2 +- .../capabilities/merge_capabilities.test.ts | 84 ------------------- .../server/capabilities/merge_capabilities.ts | 44 ---------- .../capabilities/resolve_capabilities.ts | 34 -------- src/legacy/server/kbn_server.d.ts | 5 +- 17 files changed, 125 insertions(+), 213 deletions(-) delete mode 100644 src/legacy/server/capabilities/merge_capabilities.test.ts delete mode 100644 src/legacy/server/capabilities/merge_capabilities.ts delete mode 100644 src/legacy/server/capabilities/resolve_capabilities.ts diff --git a/src/core/server/capabilities/capabilities_service.mock.ts b/src/core/server/capabilities/capabilities_service.mock.ts index c7a4bb7444356..2ce9deb83565d 100644 --- a/src/core/server/capabilities/capabilities_service.mock.ts +++ b/src/core/server/capabilities/capabilities_service.mock.ts @@ -17,7 +17,7 @@ * under the License. */ -import { CapabilitiesService, CapabilitiesSetup } from './capabilities_service'; +import { CapabilitiesService, CapabilitiesSetup, CapabilitiesStart } from './capabilities_service'; const createSetupContractMock = () => { const setupContract: jest.Mocked = { @@ -27,11 +27,18 @@ const createSetupContractMock = () => { return setupContract; }; +const createStartContractMock = () => { + const setupContract: jest.Mocked = { + resolveCapabilities: jest.fn().mockReturnValue(Promise.resolve({})), + }; + return setupContract; +}; + type CapabilitiesServiceContract = PublicMethodsOf; const createMock = () => { const mocked: jest.Mocked = { setup: jest.fn().mockReturnValue(createSetupContractMock()), - start: jest.fn(), + start: jest.fn().mockReturnValue(createStartContractMock()), }; return mocked; }; @@ -39,4 +46,5 @@ const createMock = () => { export const capabilitiesServiceMock = { create: createMock, createSetupContract: createSetupContractMock, + createStartContract: createStartContractMock, }; diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 3e2fc7ae0f44f..5270c2c275fd9 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -45,7 +45,7 @@ import { PluginsServiceSetup, PluginsServiceStart, PluginOpaqueId } from './plug import { ContextSetup } from './context'; import { IUiSettingsClient, UiSettingsServiceSetup } from './ui_settings'; import { SavedObjectsClientContract } from './saved_objects/types'; -import { CapabilitiesSetup } from './capabilities'; +import { CapabilitiesSetup, CapabilitiesStart } from './capabilities'; export { bootstrap } from './bootstrap'; export { ConfigPath, ConfigService, EnvironmentMode, PackageInfo } from './config'; @@ -244,10 +244,14 @@ export interface CoreSetup { * * @public */ -export interface CoreStart {} // eslint-disable-line @typescript-eslint/no-empty-interface +export interface CoreStart { + /** {@link CapabilitiesStart} */ + capabilities: CapabilitiesStart; +} export { CapabilitiesSetup, + CapabilitiesStart, ContextSetup, PluginsServiceSetup, PluginsServiceStart, diff --git a/src/core/server/internal_types.ts b/src/core/server/internal_types.ts index 6166ef5e82a66..fe5b2d04fccfa 100644 --- a/src/core/server/internal_types.ts +++ b/src/core/server/internal_types.ts @@ -22,7 +22,7 @@ import { InternalHttpServiceSetup } from './http'; import { InternalUiSettingsServiceSetup } from './ui_settings'; import { ContextSetup } from './context'; import { SavedObjectsServiceStart } from './saved_objects'; -import { CapabilitiesSetup } from './capabilities'; +import { CapabilitiesSetup, CapabilitiesStart } from './capabilities'; /** @internal */ export interface InternalCoreSetup { @@ -37,5 +37,6 @@ export interface InternalCoreSetup { * @internal */ export interface InternalCoreStart { + capabilities: CapabilitiesStart; savedObjects: SavedObjectsServiceStart; } diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts index 68bed8b60379f..f24272457257f 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -96,6 +96,7 @@ beforeEach(() => { startDeps = { core: { + capabilities: capabilitiesServiceMock.createStartContract(), savedObjects: { migrator: {} as KibanaMigrator, clientProvider: {} as ISavedObjectsClientProvider, diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 699ae24e1f6dd..6f4540ab3dfc8 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -254,7 +254,9 @@ export class LegacyService implements CoreService { register: setupDeps.core.uiSettings.register, }, }; - const coreStart: CoreStart = {}; + const coreStart: CoreStart = { + capabilities: startDeps.core.capabilities, + }; // eslint-disable-next-line @typescript-eslint/no-var-requires const KbnServer = require('../../../legacy/server/kbn_server'); diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index 37e91a594a14b..8811ac35e1247 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -96,7 +96,9 @@ function createCoreSetupMock() { } function createCoreStartMock() { - const mock: MockedKeys = {}; + const mock: MockedKeys = { + capabilities: capabilitiesServiceMock.createStartContract(), + }; return mock; } diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index a4ede8b9f1a14..9a9142ca8f4b7 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -150,5 +150,9 @@ export function createPluginStartContext( deps: PluginsServiceStartDeps, plugin: PluginWrapper ): CoreStart { - return {}; + return { + capabilities: { + resolveCapabilities: deps.capabilities.resolveCapabilities, + }, + }; } diff --git a/src/core/server/plugins/plugins_service.ts b/src/core/server/plugins/plugins_service.ts index 4c73c2a304dc4..f5b7076749600 100644 --- a/src/core/server/plugins/plugins_service.ts +++ b/src/core/server/plugins/plugins_service.ts @@ -28,7 +28,7 @@ import { PluginWrapper } from './plugin'; import { DiscoveredPlugin, PluginConfigDescriptor, PluginName, InternalPluginInfo } from './types'; import { PluginsConfig, PluginsConfigType } from './plugins_config'; import { PluginsSystem } from './plugins_system'; -import { InternalCoreSetup } from '../internal_types'; +import { InternalCoreSetup, InternalCoreStart } from '../internal_types'; import { IConfigService } from '../config'; import { pick } from '../../utils'; @@ -63,7 +63,9 @@ export interface PluginsServiceStart { export type PluginsServiceSetupDeps = InternalCoreSetup; /** @internal */ -export interface PluginsServiceStartDeps {} // eslint-disable-line @typescript-eslint/no-empty-interface +export interface PluginsServiceStartDeps { + capabilities: InternalCoreStart['capabilities']; +} /** @internal */ export class PluginsService implements CoreService { diff --git a/src/core/server/plugins/plugins_system.test.ts b/src/core/server/plugins/plugins_system.test.ts index 6f1788f717f61..6a26fed78269c 100644 --- a/src/core/server/plugins/plugins_system.test.ts +++ b/src/core/server/plugins/plugins_system.test.ts @@ -29,6 +29,7 @@ import { getEnvOptions } from '../config/__mocks__/env'; import { CoreContext } from '../core_context'; import { configServiceMock } from '../config/config_service.mock'; import { loggingServiceMock } from '../logging/logging_service.mock'; +import { capabilitiesServiceMock } from '../capabilities/capabilities_service.mock'; import { PluginWrapper } from './plugin'; import { PluginName } from './types'; @@ -69,6 +70,9 @@ configService.atPath.mockReturnValue(new BehaviorSubject({ initialize: true })); let env: Env; let coreContext: CoreContext; const setupDeps = coreMock.createInternalSetup(); +const startDeps = { + capabilities: capabilitiesServiceMock.createStartContract(), +}; beforeEach(() => { env = Env.createDefault(getEnvOptions()); @@ -249,7 +253,6 @@ test('correctly orders plugins and returns exposed values for "setup" and "start expect(plugin.setup).toHaveBeenCalledWith(setupContextMap.get(plugin.name), deps.setup); } - const startDeps = {}; expect([...(await pluginsSystem.startPlugins(startDeps))]).toMatchInlineSnapshot(` Array [ Array [ @@ -382,7 +385,7 @@ test('`uiPlugins` returns only ui plugin dependencies', async () => { test('can start without plugins', async () => { await pluginsSystem.setupPlugins(setupDeps); - const pluginsStart = await pluginsSystem.startPlugins({}); + const pluginsStart = await pluginsSystem.startPlugins(startDeps); expect(pluginsStart).toBeInstanceOf(Map); expect(pluginsStart.size).toBe(0); @@ -400,7 +403,7 @@ test('`startPlugins` only starts plugins that were setup', async () => { pluginsSystem.addPlugin(plugin); }); await pluginsSystem.setupPlugins(setupDeps); - const result = await pluginsSystem.startPlugins({}); + const result = await pluginsSystem.startPlugins(startDeps); expect([...result]).toMatchInlineSnapshot(` Array [ Array [ diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 594f216bd9521..0239058034768 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -135,10 +135,14 @@ export class Server { public async start() { this.log.debug('starting server'); - const pluginsStart = await this.plugins.start({}); const savedObjectsStart = await this.savedObjects.start({}); + const capabilitiesStart = this.capabilities.start(); + const pluginsStart = await this.plugins.start({ + capabilities: capabilitiesStart, + }); const coreStart = { + capabilities: capabilitiesStart, savedObjects: savedObjectsStart, plugins: pluginsStart, }; diff --git a/src/legacy/server/capabilities/capabilities_mixin.test.ts b/src/legacy/server/capabilities/capabilities_mixin.test.ts index bf78a31bbf5da..49d8294d4aef6 100644 --- a/src/legacy/server/capabilities/capabilities_mixin.test.ts +++ b/src/legacy/server/capabilities/capabilities_mixin.test.ts @@ -23,16 +23,29 @@ import KbnServer from '../kbn_server'; import { capabilitiesMixin } from './capabilities_mixin'; describe('capabilitiesMixin', () => { + let registerMock: jest.Mock; + const getKbnServer = (pluginSpecs: any[] = []) => { - return { + return ({ afterPluginsInit: (callback: () => void) => callback(), pluginSpecs, - } as KbnServer; + newPlatform: { + setup: { + core: { + capabilities: { + registerCapabilitiesProvider: registerMock, + }, + }, + }, + }, + } as unknown) as KbnServer; }; let server: Server; beforeEach(() => { server = new Server(); + server.getUiNavLinks = () => []; + registerMock = jest.fn(); }); it('exposes request#getCapabilities for retrieving legacy capabilities', async () => { @@ -45,4 +58,33 @@ describe('capabilitiesMixin', () => { expect.any(Function) ); }); + + it('calls capabilities#registerCapabilitiesProvider for each legacy plugin specs', async () => { + const getPluginSpec = (provider: () => any) => ({ + getUiCapabilitiesProvider: () => provider, + }); + + const capaA = { catalogue: { A: true } }; + const capaB = { catalogue: { B: true } }; + const kbnServer = getKbnServer([getPluginSpec(() => capaA), getPluginSpec(() => capaB)]); + await capabilitiesMixin(kbnServer, server); + + expect(registerMock).toHaveBeenCalledTimes(2); + expect(registerMock.mock.calls[0][0]()).toEqual(capaA); + expect(registerMock.mock.calls[1][0]()).toEqual(capaB); + }); + + it('calls capabilities#registerCapabilitiesProvider for navLinks', async () => { + const kbnServer = getKbnServer(); + server.getUiNavLinks = () => [{ _id: 'A' }, { _id: 'B' }]; + await capabilitiesMixin(kbnServer, server); + + expect(registerMock).toHaveBeenCalledTimes(1); + expect(registerMock.mock.calls[0][0]()).toEqual({ + navLinks: { + A: true, + B: true, + }, + }); + }); }); diff --git a/src/legacy/server/capabilities/capabilities_mixin.ts b/src/legacy/server/capabilities/capabilities_mixin.ts index 12708f17fbf72..8a680f29d0599 100644 --- a/src/legacy/server/capabilities/capabilities_mixin.ts +++ b/src/legacy/server/capabilities/capabilities_mixin.ts @@ -17,44 +17,46 @@ * under the License. */ -import { Server, Request } from 'hapi'; - -import { Capabilities } from '../../../core/public'; +import { Server } from 'hapi'; +import { KibanaRequest } from '../../../core/server'; import KbnServer from '../kbn_server'; -import { mergeCapabilities } from './merge_capabilities'; -import { resolveCapabilities } from './resolve_capabilities'; - -export type CapabilitiesModifier = ( - request: Request, - uiCapabilities: Capabilities -) => Capabilities | Promise; export async function capabilitiesMixin(kbnServer: KbnServer, server: Server) { - const modifiers: CapabilitiesModifier[] = []; + const registerLegacyCapabilities = async () => { + const capabilitiesList = await Promise.all( + kbnServer.pluginSpecs + .map(spec => spec.getUiCapabilitiesProvider()) + .filter(provider => !!provider) + .map(provider => provider(server)) + ); + // Get legacy nav links + const navLinks = server.getUiNavLinks().reduce( + (acc, spec) => ({ + ...acc, + [spec._id]: true, + }), + {} as Record + ); + if (Object.keys(navLinks).length) { + capabilitiesList.push({ navLinks }); + } + + capabilitiesList.forEach(capabilities => { + kbnServer.newPlatform.setup.core.capabilities.registerCapabilitiesProvider( + () => capabilities + ); + }); + }; // Some plugin capabilities are derived from data provided by other plugins, // so we need to wait until after all plugins have been init'd to fetch uiCapabilities. kbnServer.afterPluginsInit(async () => { - const defaultCapabilities = mergeCapabilities( - ...(await Promise.all( - kbnServer.pluginSpecs - .map(spec => spec.getUiCapabilitiesProvider()) - .filter(provider => !!provider) - .map(provider => provider(server)) - )) - ); - - server.decorate('request', 'getCapabilities', function() { - // Get legacy nav links - const navLinks = server.getUiNavLinks().reduce( - (acc, spec) => ({ - ...acc, - [spec._id]: true, - }), - {} as Record - ); + await registerLegacyCapabilities(); + }); - return resolveCapabilities(this, modifiers, defaultCapabilities, { navLinks }); - }); + server.decorate('request', 'getCapabilities', function() { + return kbnServer.newPlatform.start.core.capabilities.resolveCapabilities( + KibanaRequest.from(this) + ); }); } diff --git a/src/legacy/server/capabilities/index.ts b/src/legacy/server/capabilities/index.ts index 09461a40c008a..8c5dea1226f2b 100644 --- a/src/legacy/server/capabilities/index.ts +++ b/src/legacy/server/capabilities/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { CapabilitiesModifier, capabilitiesMixin } from './capabilities_mixin'; +export { capabilitiesMixin } from './capabilities_mixin'; diff --git a/src/legacy/server/capabilities/merge_capabilities.test.ts b/src/legacy/server/capabilities/merge_capabilities.test.ts deleted file mode 100644 index a73b81bdaf0a3..0000000000000 --- a/src/legacy/server/capabilities/merge_capabilities.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { mergeCapabilities } from './merge_capabilities'; - -const defaultProps = { - catalogue: {}, - management: {}, - navLinks: {}, -}; - -test(`"{ foo: {} }" doesn't clobber "{ foo: { bar: true } }"`, () => { - const output1 = mergeCapabilities({ foo: { bar: true } }, { foo: {} }); - expect(output1).toEqual({ ...defaultProps, foo: { bar: true } }); - - const output2 = mergeCapabilities({ foo: { bar: true } }, { foo: {} }); - expect(output2).toEqual({ ...defaultProps, foo: { bar: true } }); -}); - -test(`"{ foo: { bar: true } }" doesn't clobber "{ baz: { quz: true } }"`, () => { - const output1 = mergeCapabilities({ foo: { bar: true } }, { baz: { quz: true } }); - expect(output1).toEqual({ ...defaultProps, foo: { bar: true }, baz: { quz: true } }); - - const output2 = mergeCapabilities({ baz: { quz: true } }, { foo: { bar: true } }); - expect(output2).toEqual({ ...defaultProps, foo: { bar: true }, baz: { quz: true } }); -}); - -test(`"{ foo: { bar: { baz: true } } }" doesn't clobber "{ foo: { bar: { quz: true } } }"`, () => { - const output1 = mergeCapabilities( - { foo: { bar: { baz: true } } }, - { foo: { bar: { quz: true } } } - ); - expect(output1).toEqual({ ...defaultProps, foo: { bar: { baz: true, quz: true } } }); - - const output2 = mergeCapabilities( - { foo: { bar: { quz: true } } }, - { foo: { bar: { baz: true } } } - ); - expect(output2).toEqual({ ...defaultProps, foo: { bar: { baz: true, quz: true } } }); -}); - -test(`error is thrown if boolean and object clash`, () => { - expect(() => { - mergeCapabilities({ foo: { bar: { baz: true } } }, { foo: { bar: true } }); - }).toThrowErrorMatchingInlineSnapshot(`"a boolean and an object can't be merged"`); - - expect(() => { - mergeCapabilities({ foo: { bar: true } }, { foo: { bar: { baz: true } } }); - }).toThrowErrorMatchingInlineSnapshot(`"a boolean and an object can't be merged"`); -}); - -test(`supports duplicates as long as the booleans are the same`, () => { - const output1 = mergeCapabilities({ foo: { bar: true } }, { foo: { bar: true } }); - expect(output1).toEqual({ ...defaultProps, foo: { bar: true } }); - - const output2 = mergeCapabilities({ foo: { bar: false } }, { foo: { bar: false } }); - expect(output2).toEqual({ ...defaultProps, foo: { bar: false } }); -}); - -test(`error is thrown if merging "true" and "false"`, () => { - expect(() => { - mergeCapabilities({ foo: { bar: false } }, { foo: { bar: true } }); - }).toThrowErrorMatchingInlineSnapshot(`"\\"true\\" and \\"false\\" can't be merged"`); - - expect(() => { - mergeCapabilities({ foo: { bar: true } }, { foo: { bar: false } }); - }).toThrowErrorMatchingInlineSnapshot(`"\\"true\\" and \\"false\\" can't be merged"`); -}); diff --git a/src/legacy/server/capabilities/merge_capabilities.ts b/src/legacy/server/capabilities/merge_capabilities.ts deleted file mode 100644 index 5fe31775ba32d..0000000000000 --- a/src/legacy/server/capabilities/merge_capabilities.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import typeDetect from 'type-detect'; -import { merge } from 'lodash'; -import { Capabilities } from '../../../core/public'; - -export const mergeCapabilities = (...sources: Array>): Capabilities => - merge( - { - navLinks: {}, - management: {}, - catalogue: {}, - }, - ...sources, - (a: any, b: any) => { - if ( - (typeDetect(a) === 'boolean' && typeDetect(b) === 'Object') || - (typeDetect(b) === 'boolean' && typeDetect(a) === 'Object') - ) { - throw new Error(`a boolean and an object can't be merged`); - } - - if (typeDetect(a) === 'boolean' && typeDetect(b) === 'boolean' && a !== b) { - throw new Error(`"true" and "false" can't be merged`); - } - } - ); diff --git a/src/legacy/server/capabilities/resolve_capabilities.ts b/src/legacy/server/capabilities/resolve_capabilities.ts deleted file mode 100644 index 0df4932099b54..0000000000000 --- a/src/legacy/server/capabilities/resolve_capabilities.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Request } from 'hapi'; - -import { Capabilities } from '../../../core/public'; -import { mergeCapabilities } from './merge_capabilities'; -import { CapabilitiesModifier } from './capabilities_mixin'; - -export const resolveCapabilities = ( - request: Request, - modifiers: CapabilitiesModifier[], - ...capabilities: Array> -) => - modifiers.reduce( - async (resolvedCaps, modifier) => modifier(request, await resolvedCaps), - Promise.resolve(mergeCapabilities(...capabilities)) - ); diff --git a/src/legacy/server/kbn_server.d.ts b/src/legacy/server/kbn_server.d.ts index 2f38ff8cb4125..d347908456f00 100644 --- a/src/legacy/server/kbn_server.d.ts +++ b/src/legacy/server/kbn_server.d.ts @@ -20,7 +20,7 @@ import { ResponseObject, Server } from 'hapi'; import { UnwrapPromise } from '@kbn/utility-types'; -import { SavedObjectsClientProviderOptions, CoreSetup } from 'src/core/server'; +import { SavedObjectsClientProviderOptions, CoreSetup, CoreStart } from 'src/core/server'; import { ConfigService, ElasticsearchServiceSetup, @@ -39,7 +39,6 @@ import { SavedObjectsManagement } from '../../core/server/saved_objects/manageme import { ApmOssPlugin } from '../core_plugins/apm_oss'; import { CallClusterWithRequest, ElasticsearchPlugin } from '../core_plugins/elasticsearch'; -import { CapabilitiesModifier } from './capabilities'; import { IndexPatternsServiceFactory } from './index_patterns'; import { Capabilities } from '../../core/public'; import { UiSettingsServiceFactoryOptions } from '../../legacy/ui/ui_settings/ui_settings_service_factory'; @@ -122,7 +121,7 @@ export default class KbnServer { plugins: Record; }; start: { - core: CoreSetup; + core: CoreStart; plugins: Record; }; stop: null; From ca62942ddd72308a7b56f9fbcc551d1712c795ac Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Fri, 22 Nov 2019 14:10:09 +0100 Subject: [PATCH 07/34] use server-side Capabilities import in server code --- .../capabilities/merge_capabilities.ts | 39 ------------------- src/core/server/index.ts | 1 + .../plugin_spec/plugin_spec_options.d.ts | 2 +- src/legacy/plugin_discovery/types.ts | 2 +- src/legacy/server/kbn_server.d.ts | 2 +- .../plugins/features/server/feature_schema.ts | 2 +- x-pack/plugins/features/server/plugin.ts | 2 +- .../server/ui_capabilities_for_features.ts | 2 +- .../server/authorization/actions/ui.ts | 2 +- 9 files changed, 8 insertions(+), 46 deletions(-) delete mode 100644 src/core/public/application/capabilities/merge_capabilities.ts diff --git a/src/core/public/application/capabilities/merge_capabilities.ts b/src/core/public/application/capabilities/merge_capabilities.ts deleted file mode 100644 index 55841717985b7..0000000000000 --- a/src/core/public/application/capabilities/merge_capabilities.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Capabilities } from './capabilities_service'; - -export const mergeCapabilities = (...sources: Array>) => - sources.reduce( - (capabilities, source) => { - Object.entries(source).forEach(([key, value]) => { - capabilities[key] = { - ...value, - ...capabilities[key], - }; - }); - - return capabilities; - }, - { - navLinks: {}, - management: {}, - catalogue: {}, - } - ); diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 5270c2c275fd9..09fb6a76f7a58 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -48,6 +48,7 @@ import { SavedObjectsClientContract } from './saved_objects/types'; import { CapabilitiesSetup, CapabilitiesStart } from './capabilities'; export { bootstrap } from './bootstrap'; +export { Capabilities } from './capabilities'; export { ConfigPath, ConfigService, EnvironmentMode, PackageInfo } from './config'; export { IContextContainer, diff --git a/src/legacy/plugin_discovery/plugin_spec/plugin_spec_options.d.ts b/src/legacy/plugin_discovery/plugin_spec/plugin_spec_options.d.ts index 6e087e3ee7f87..228ef96f8c9f3 100644 --- a/src/legacy/plugin_discovery/plugin_spec/plugin_spec_options.d.ts +++ b/src/legacy/plugin_discovery/plugin_spec/plugin_spec_options.d.ts @@ -17,7 +17,7 @@ * under the License. */ import { Server } from '../../server/kbn_server'; -import { Capabilities } from '../../../core/public'; +import { Capabilities } from '../../../core/server'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { SavedObjectsManagementDefinition } from '../../../core/server/saved_objects/management'; diff --git a/src/legacy/plugin_discovery/types.ts b/src/legacy/plugin_discovery/types.ts index d987260b099bd..c32418e1aeb62 100644 --- a/src/legacy/plugin_discovery/types.ts +++ b/src/legacy/plugin_discovery/types.ts @@ -18,7 +18,7 @@ */ import { Server } from '../server/kbn_server'; -import { Capabilities } from '../../core/public'; +import { Capabilities } from '../../core/server'; // Disable lint errors for imports from src/core/* until SavedObjects migration is complete // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { SavedObjectsSchemaDefinition } from '../../core/server/saved_objects/schema'; diff --git a/src/legacy/server/kbn_server.d.ts b/src/legacy/server/kbn_server.d.ts index d347908456f00..37db03688a5c8 100644 --- a/src/legacy/server/kbn_server.d.ts +++ b/src/legacy/server/kbn_server.d.ts @@ -40,7 +40,7 @@ import { ApmOssPlugin } from '../core_plugins/apm_oss'; import { CallClusterWithRequest, ElasticsearchPlugin } from '../core_plugins/elasticsearch'; import { IndexPatternsServiceFactory } from './index_patterns'; -import { Capabilities } from '../../core/public'; +import { Capabilities } from '../../core/server'; import { UiSettingsServiceFactoryOptions } from '../../legacy/ui/ui_settings/ui_settings_service_factory'; export interface KibanaConfig { diff --git a/x-pack/plugins/features/server/feature_schema.ts b/x-pack/plugins/features/server/feature_schema.ts index 9ae55a61c910f..b5ba10f8d0300 100644 --- a/x-pack/plugins/features/server/feature_schema.ts +++ b/x-pack/plugins/features/server/feature_schema.ts @@ -7,7 +7,7 @@ import Joi from 'joi'; import { difference } from 'lodash'; -import { Capabilities as UICapabilities } from '../../../../src/core/public'; +import { Capabilities as UICapabilities } from '../../../../src/core/server'; import { FeatureWithAllOrReadPrivileges } from './feature'; // Each feature gets its own property on the UICapabilities object, diff --git a/x-pack/plugins/features/server/plugin.ts b/x-pack/plugins/features/server/plugin.ts index ce7fea129bb6f..f3a3375d1936b 100644 --- a/x-pack/plugins/features/server/plugin.ts +++ b/x-pack/plugins/features/server/plugin.ts @@ -10,7 +10,7 @@ import { PluginInitializerContext, RecursiveReadonly, } from '../../../../src/core/server'; -import { Capabilities as UICapabilities } from '../../../../src/core/public'; +import { Capabilities as UICapabilities } from '../../../../src/core/server'; import { deepFreeze } from '../../../../src/core/utils'; import { XPackInfo } from '../../../legacy/plugins/xpack_main/server/lib/xpack_info'; import { PluginSetupContract as TimelionSetupContract } from '../../../../src/plugins/timelion/server'; diff --git a/x-pack/plugins/features/server/ui_capabilities_for_features.ts b/x-pack/plugins/features/server/ui_capabilities_for_features.ts index 22c9379686b34..368b38ce7df91 100644 --- a/x-pack/plugins/features/server/ui_capabilities_for_features.ts +++ b/x-pack/plugins/features/server/ui_capabilities_for_features.ts @@ -5,7 +5,7 @@ */ import _ from 'lodash'; -import { Capabilities as UICapabilities } from '../../../../src/core/public'; +import { Capabilities as UICapabilities } from '../../../../src/core/server'; import { Feature } from './feature'; const ELIGIBLE_FLAT_MERGE_KEYS = ['catalogue']; diff --git a/x-pack/plugins/security/server/authorization/actions/ui.ts b/x-pack/plugins/security/server/authorization/actions/ui.ts index c243b4f0bbdc1..9e77c319a9b3a 100644 --- a/x-pack/plugins/security/server/authorization/actions/ui.ts +++ b/x-pack/plugins/security/server/authorization/actions/ui.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { isString } from 'lodash'; -import { Capabilities as UICapabilities } from '../../../../../../src/core/public'; +import { Capabilities as UICapabilities } from '../../../../../../src/core/server'; import { uiCapabilitiesRegex } from '../../../../features/server'; export class UIActions { From 0d2755911000a1e2a38ca5f1e66739eb384d00fc Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Fri, 22 Nov 2019 14:13:34 +0100 Subject: [PATCH 08/34] update generated doc --- ...na-plugin-server.capabilities.catalogue.md | 13 +++++++ ...a-plugin-server.capabilities.management.md | 15 ++++++++ .../kibana-plugin-server.capabilities.md | 22 ++++++++++++ ...ana-plugin-server.capabilities.navlinks.md | 13 +++++++ .../kibana-plugin-server.capabilitiessetup.md | 19 ++++++++++ ...itiessetup.registercapabilitiesprovider.md | 22 ++++++++++++ ...itiessetup.registercapabilitiesswitcher.md | 22 ++++++++++++ .../kibana-plugin-server.capabilitiesstart.md | 18 ++++++++++ ...r.capabilitiesstart.resolvecapabilities.md | 22 ++++++++++++ ...na-plugin-server.coresetup.capabilities.md | 13 +++++++ .../server/kibana-plugin-server.coresetup.md | 1 + ...na-plugin-server.corestart.capabilities.md | 13 +++++++ .../server/kibana-plugin-server.corestart.md | 7 ++++ .../core/server/kibana-plugin-server.md | 3 ++ src/core/server/server.api.md | 36 +++++++++++++++++++ 15 files changed, 239 insertions(+) create mode 100644 docs/development/core/server/kibana-plugin-server.capabilities.catalogue.md create mode 100644 docs/development/core/server/kibana-plugin-server.capabilities.management.md create mode 100644 docs/development/core/server/kibana-plugin-server.capabilities.md create mode 100644 docs/development/core/server/kibana-plugin-server.capabilities.navlinks.md create mode 100644 docs/development/core/server/kibana-plugin-server.capabilitiessetup.md create mode 100644 docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesprovider.md create mode 100644 docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesswitcher.md create mode 100644 docs/development/core/server/kibana-plugin-server.capabilitiesstart.md create mode 100644 docs/development/core/server/kibana-plugin-server.capabilitiesstart.resolvecapabilities.md create mode 100644 docs/development/core/server/kibana-plugin-server.coresetup.capabilities.md create mode 100644 docs/development/core/server/kibana-plugin-server.corestart.capabilities.md diff --git a/docs/development/core/server/kibana-plugin-server.capabilities.catalogue.md b/docs/development/core/server/kibana-plugin-server.capabilities.catalogue.md new file mode 100644 index 0000000000000..4eb012c78f0cb --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.capabilities.catalogue.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [Capabilities](./kibana-plugin-server.capabilities.md) > [catalogue](./kibana-plugin-server.capabilities.catalogue.md) + +## Capabilities.catalogue property + +Catalogue capabilities. Catalogue entries drive the visibility of the Kibana homepage options. + +Signature: + +```typescript +catalogue: Record; +``` diff --git a/docs/development/core/server/kibana-plugin-server.capabilities.management.md b/docs/development/core/server/kibana-plugin-server.capabilities.management.md new file mode 100644 index 0000000000000..d917c81dc3720 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.capabilities.management.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [Capabilities](./kibana-plugin-server.capabilities.md) > [management](./kibana-plugin-server.capabilities.management.md) + +## Capabilities.management property + +Management section capabilities. + +Signature: + +```typescript +management: { + [sectionId: string]: Record; + }; +``` diff --git a/docs/development/core/server/kibana-plugin-server.capabilities.md b/docs/development/core/server/kibana-plugin-server.capabilities.md new file mode 100644 index 0000000000000..031282b9733ac --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.capabilities.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [Capabilities](./kibana-plugin-server.capabilities.md) + +## Capabilities interface + +The read-only set of capabilities available for the current UI session. Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, and the boolean is a flag indicating if the capability is enabled or disabled. + +Signature: + +```typescript +export interface Capabilities +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [catalogue](./kibana-plugin-server.capabilities.catalogue.md) | Record<string, boolean> | Catalogue capabilities. Catalogue entries drive the visibility of the Kibana homepage options. | +| [management](./kibana-plugin-server.capabilities.management.md) | {
[sectionId: string]: Record<string, boolean>;
} | Management section capabilities. | +| [navLinks](./kibana-plugin-server.capabilities.navlinks.md) | Record<string, boolean> | Navigation link capabilities. | + diff --git a/docs/development/core/server/kibana-plugin-server.capabilities.navlinks.md b/docs/development/core/server/kibana-plugin-server.capabilities.navlinks.md new file mode 100644 index 0000000000000..a1612ea840fbf --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.capabilities.navlinks.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [Capabilities](./kibana-plugin-server.capabilities.md) > [navLinks](./kibana-plugin-server.capabilities.navlinks.md) + +## Capabilities.navLinks property + +Navigation link capabilities. + +Signature: + +```typescript +navLinks: Record; +``` diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.md b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.md new file mode 100644 index 0000000000000..8bfb7a7d98d68 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) + +## CapabilitiesSetup interface + +Signature: + +```typescript +export interface CapabilitiesSetup +``` + +## Methods + +| Method | Description | +| --- | --- | +| [registerCapabilitiesProvider(provider)](./kibana-plugin-server.capabilitiessetup.registercapabilitiesprovider.md) | | +| [registerCapabilitiesSwitcher(switcher)](./kibana-plugin-server.capabilitiessetup.registercapabilitiesswitcher.md) | | + diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesprovider.md b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesprovider.md new file mode 100644 index 0000000000000..5af88914ec8e9 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesprovider.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) > [registerCapabilitiesProvider](./kibana-plugin-server.capabilitiessetup.registercapabilitiesprovider.md) + +## CapabilitiesSetup.registerCapabilitiesProvider() method + +Signature: + +```typescript +registerCapabilitiesProvider(provider: CapabilitiesProvider): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| provider | CapabilitiesProvider | | + +Returns: + +`void` + diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesswitcher.md b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesswitcher.md new file mode 100644 index 0000000000000..cfa0c31b1fa78 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesswitcher.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) > [registerCapabilitiesSwitcher](./kibana-plugin-server.capabilitiessetup.registercapabilitiesswitcher.md) + +## CapabilitiesSetup.registerCapabilitiesSwitcher() method + +Signature: + +```typescript +registerCapabilitiesSwitcher(switcher: CapabilitiesSwitcher): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| switcher | CapabilitiesSwitcher | | + +Returns: + +`void` + diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiesstart.md b/docs/development/core/server/kibana-plugin-server.capabilitiesstart.md new file mode 100644 index 0000000000000..a8ae9b9e66116 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.capabilitiesstart.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CapabilitiesStart](./kibana-plugin-server.capabilitiesstart.md) + +## CapabilitiesStart interface + +Signature: + +```typescript +export interface CapabilitiesStart +``` + +## Methods + +| Method | Description | +| --- | --- | +| [resolveCapabilities(request)](./kibana-plugin-server.capabilitiesstart.resolvecapabilities.md) | | + diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiesstart.resolvecapabilities.md b/docs/development/core/server/kibana-plugin-server.capabilitiesstart.resolvecapabilities.md new file mode 100644 index 0000000000000..cc902eea08e5c --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.capabilitiesstart.resolvecapabilities.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CapabilitiesStart](./kibana-plugin-server.capabilitiesstart.md) > [resolveCapabilities](./kibana-plugin-server.capabilitiesstart.resolvecapabilities.md) + +## CapabilitiesStart.resolveCapabilities() method + +Signature: + +```typescript +resolveCapabilities(request: KibanaRequest): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| request | KibanaRequest | | + +Returns: + +`Promise` + diff --git a/docs/development/core/server/kibana-plugin-server.coresetup.capabilities.md b/docs/development/core/server/kibana-plugin-server.coresetup.capabilities.md new file mode 100644 index 0000000000000..fe50347d97e3c --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.coresetup.capabilities.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreSetup](./kibana-plugin-server.coresetup.md) > [capabilities](./kibana-plugin-server.coresetup.capabilities.md) + +## CoreSetup.capabilities property + +[CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) + +Signature: + +```typescript +capabilities: CapabilitiesSetup; +``` diff --git a/docs/development/core/server/kibana-plugin-server.coresetup.md b/docs/development/core/server/kibana-plugin-server.coresetup.md index c51459bc41a43..82c62ee04c1a8 100644 --- a/docs/development/core/server/kibana-plugin-server.coresetup.md +++ b/docs/development/core/server/kibana-plugin-server.coresetup.md @@ -16,6 +16,7 @@ export interface CoreSetup | Property | Type | Description | | --- | --- | --- | +| [capabilities](./kibana-plugin-server.coresetup.capabilities.md) | CapabilitiesSetup | [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | | [context](./kibana-plugin-server.coresetup.context.md) | ContextSetup | [ContextSetup](./kibana-plugin-server.contextsetup.md) | | [elasticsearch](./kibana-plugin-server.coresetup.elasticsearch.md) | ElasticsearchServiceSetup | [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) | | [http](./kibana-plugin-server.coresetup.http.md) | HttpServiceSetup | [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) | diff --git a/docs/development/core/server/kibana-plugin-server.corestart.capabilities.md b/docs/development/core/server/kibana-plugin-server.corestart.capabilities.md new file mode 100644 index 0000000000000..03930d367ee75 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.corestart.capabilities.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreStart](./kibana-plugin-server.corestart.md) > [capabilities](./kibana-plugin-server.corestart.capabilities.md) + +## CoreStart.capabilities property + +[CapabilitiesStart](./kibana-plugin-server.capabilitiesstart.md) + +Signature: + +```typescript +capabilities: CapabilitiesStart; +``` diff --git a/docs/development/core/server/kibana-plugin-server.corestart.md b/docs/development/core/server/kibana-plugin-server.corestart.md index da80ae8be93af..4a636713a9a84 100644 --- a/docs/development/core/server/kibana-plugin-server.corestart.md +++ b/docs/development/core/server/kibana-plugin-server.corestart.md @@ -11,3 +11,10 @@ Context passed to the plugins `start` method. ```typescript export interface CoreStart ``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [capabilities](./kibana-plugin-server.corestart.capabilities.md) | CapabilitiesStart | [CapabilitiesStart](./kibana-plugin-server.capabilitiesstart.md) | + diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 360675b3490c2..6ea87b58c2844 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -42,6 +42,9 @@ The plugin integrates with the core system via lifecycle events: `setup` | [AuthResultParams](./kibana-plugin-server.authresultparams.md) | Result of an incoming request authentication. | | [AuthToolkit](./kibana-plugin-server.authtoolkit.md) | A tool set defining an outcome of Auth interceptor for incoming request. | | [CallAPIOptions](./kibana-plugin-server.callapioptions.md) | The set of options that defines how API call should be made and result be processed. | +| [Capabilities](./kibana-plugin-server.capabilities.md) | The read-only set of capabilities available for the current UI session. Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, and the boolean is a flag indicating if the capability is enabled or disabled. | +| [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | | +| [CapabilitiesStart](./kibana-plugin-server.capabilitiesstart.md) | | | [ContextSetup](./kibana-plugin-server.contextsetup.md) | An object that handles registration of context providers and configuring handlers with context. | | [CoreSetup](./kibana-plugin-server.coresetup.md) | Context passed to the plugins setup method. | | [CoreStart](./kibana-plugin-server.corestart.md) | Context passed to the plugins start method. | diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index d6cfa54397565..7cba4f0e8e448 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -467,6 +467,38 @@ export interface CallAPIOptions { wrap401Errors?: boolean; } +// @public +export interface Capabilities { + [key: string]: Record>; + catalogue: Record; + management: { + [sectionId: string]: Record; + }; + navLinks: Record; +} + +// Warning: (ae-missing-release-tag) "CapabilitiesSetup" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface CapabilitiesSetup { + // Warning: (ae-forgotten-export) The symbol "CapabilitiesProvider" needs to be exported by the entry point index.d.ts + // + // (undocumented) + registerCapabilitiesProvider(provider: CapabilitiesProvider): void; + // Warning: (ae-forgotten-export) The symbol "CapabilitiesSwitcher" needs to be exported by the entry point index.d.ts + // + // (undocumented) + registerCapabilitiesSwitcher(switcher: CapabilitiesSwitcher): void; +} + +// Warning: (ae-missing-release-tag) "CapabilitiesStart" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface CapabilitiesStart { + // (undocumented) + resolveCapabilities(request: KibanaRequest): Promise; +} + // @public export class ClusterClient implements IClusterClient { constructor(config: ElasticsearchClientConfig, log: Logger, getAuthHeaders?: GetAuthHeaders); @@ -505,6 +537,8 @@ export type CoreId = symbol; // @public export interface CoreSetup { + // (undocumented) + capabilities: CapabilitiesSetup; // (undocumented) context: ContextSetup; // (undocumented) @@ -517,6 +551,8 @@ export interface CoreSetup { // @public export interface CoreStart { + // (undocumented) + capabilities: CapabilitiesStart; } // @public From 85b8645e2477de74f0163394e2b41409842bbc21 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Mon, 25 Nov 2019 09:31:59 +0100 Subject: [PATCH 09/34] remove capabilities from injectedMetadatas --- .../application/application_service.test.tsx | 4 +- .../application/application_service.tsx | 2 +- .../capabilities/capabilities_service.test.ts | 42 ++++++++++--------- .../capabilities/capabilities_service.tsx | 24 +++++++---- src/core/public/http/http_service.mock.ts | 4 +- .../injected_metadata_service.mock.ts | 2 - .../injected_metadata_service.ts | 7 ---- .../capabilities/capabilities_service.ts | 12 ++++-- .../capabilities/capabilities_mixin.test.ts | 11 ----- .../server/capabilities/capabilities_mixin.ts | 7 ---- src/legacy/server/kbn_server.d.ts | 1 - src/legacy/ui/ui_render/ui_render_mixin.js | 2 - 12 files changed, 51 insertions(+), 67 deletions(-) diff --git a/src/core/public/application/application_service.test.tsx b/src/core/public/application/application_service.test.tsx index 5b374218a5932..19a208aeefb37 100644 --- a/src/core/public/application/application_service.test.tsx +++ b/src/core/public/application/application_service.test.tsx @@ -136,7 +136,7 @@ describe('#start()', () => { expect(MockCapabilitiesService.start).toHaveBeenCalledWith({ apps: new Map([['app1', { id: 'app1' }]]), legacyApps: new Map(), - injectedMetadata, + http, }); }); @@ -153,7 +153,7 @@ describe('#start()', () => { expect(MockCapabilitiesService.start).toHaveBeenCalledWith({ apps: new Map(), legacyApps: new Map([['legacyApp1', { id: 'legacyApp1' }]]), - injectedMetadata, + http, }); }); diff --git a/src/core/public/application/application_service.tsx b/src/core/public/application/application_service.tsx index 935844baddf86..45ca7f3fe7f7b 100644 --- a/src/core/public/application/application_service.tsx +++ b/src/core/public/application/application_service.tsx @@ -111,9 +111,9 @@ export class ApplicationService { const legacyMode = injectedMetadata.getLegacyMode(); const currentAppId$ = new BehaviorSubject(undefined); const { availableApps, availableLegacyApps, capabilities } = await this.capabilities.start({ + http, apps: new Map([...this.apps$.value].map(([id, { app }]) => [id, app])), legacyApps: this.legacyApps$.value, - injectedMetadata, }); // Only setup history if we're not in legacy mode diff --git a/src/core/public/application/capabilities/capabilities_service.test.ts b/src/core/public/application/capabilities/capabilities_service.test.ts index e80e9a7af321a..7de0f3c26c98c 100644 --- a/src/core/public/application/capabilities/capabilities_service.test.ts +++ b/src/core/public/application/capabilities/capabilities_service.test.ts @@ -17,28 +17,30 @@ * under the License. */ -import { InjectedMetadataService } from '../../injected_metadata'; +import { httpServiceMock, HttpSetupMock } from '../../http/http_service.mock'; import { CapabilitiesService } from './capabilities_service'; import { LegacyApp, App } from '../types'; +const mockedCapabilities = { + catalogue: {}, + management: {}, + navLinks: { + app1: true, + app2: false, + legacyApp1: true, + legacyApp2: false, + }, + foo: { feature: true }, + bar: { feature: true }, +}; + describe('#start', () => { - const injectedMetadata = new InjectedMetadataService({ - injectedMetadata: { - version: 'kibanaVersion', - capabilities: { - catalogue: {}, - management: {}, - navLinks: { - app1: true, - app2: false, - legacyApp1: true, - legacyApp2: false, - }, - foo: { feature: true }, - bar: { feature: true }, - }, - } as any, - }).start(); + let http: HttpSetupMock; + + beforeEach(() => { + http = httpServiceMock.createStartContract(); + http.post.mockReturnValue(Promise.resolve(mockedCapabilities)); + }); const apps = new Map([ ['app1', { id: 'app1' }], @@ -51,7 +53,7 @@ describe('#start', () => { it('filters available apps based on returned navLinks', async () => { const service = new CapabilitiesService(); - const startContract = await service.start({ apps, legacyApps, injectedMetadata }); + const startContract = await service.start({ apps, legacyApps, http }); expect(startContract.availableApps).toEqual(new Map([['app1', { id: 'app1' }]])); expect(startContract.availableLegacyApps).toEqual( new Map([['legacyApp1', { id: 'legacyApp1' }]]) @@ -63,7 +65,7 @@ describe('#start', () => { const { capabilities } = await service.start({ apps, legacyApps, - injectedMetadata, + http, }); // @ts-ignore TypeScript knows this shouldn't be possible diff --git a/src/core/public/application/capabilities/capabilities_service.tsx b/src/core/public/application/capabilities/capabilities_service.tsx index b080f8c138cf2..79cf905935246 100644 --- a/src/core/public/application/capabilities/capabilities_service.tsx +++ b/src/core/public/application/capabilities/capabilities_service.tsx @@ -19,12 +19,12 @@ import { deepFreeze, RecursiveReadonly } from '../../../utils'; import { LegacyApp, App } from '../types'; -import { InjectedMetadataStart } from '../../injected_metadata'; +import { HttpStart } from '../../http'; interface StartDeps { apps: ReadonlyMap; legacyApps: ReadonlyMap; - injectedMetadata: InjectedMetadataStart; + http: HttpStart; } /** @@ -62,12 +62,10 @@ export interface CapabilitiesStart { * @internal */ export class CapabilitiesService { - public async start({ - apps, - legacyApps, - injectedMetadata, - }: StartDeps): Promise { - const capabilities = deepFreeze(injectedMetadata.getCapabilities()); + public async start({ apps, legacyApps, http }: StartDeps): Promise { + const capabilities = await this.fetchCapabilities(http, [...apps.keys(), ...legacyApps.keys()]); + // console.log('response:', capabilities); + const availableApps = new Map( [...apps].filter( ([appId]) => @@ -88,4 +86,14 @@ export class CapabilitiesService { capabilities, }; } + + private async fetchCapabilities(http: HttpStart, appIds: string[]): Promise { + return deepFreeze( + await http.post('/core/capabilities', { + body: JSON.stringify({ + applications: appIds, + }), + }) + ); + } } diff --git a/src/core/public/http/http_service.mock.ts b/src/core/public/http/http_service.mock.ts index 52f188c7b20a0..fe7c749091b03 100644 --- a/src/core/public/http/http_service.mock.ts +++ b/src/core/public/http/http_service.mock.ts @@ -23,11 +23,11 @@ import { BehaviorSubject } from 'rxjs'; import { BasePath } from './base_path_service'; import { AnonymousPaths } from './anonymous_paths'; -type ServiceSetupMockType = jest.Mocked & { +export type HttpSetupMock = jest.Mocked & { basePath: BasePath; }; -const createServiceMock = ({ basePath = '' } = {}): ServiceSetupMockType => ({ +const createServiceMock = ({ basePath = '' } = {}): HttpSetupMock => ({ fetch: jest.fn(), get: jest.fn(), head: jest.fn(), diff --git a/src/core/public/injected_metadata/injected_metadata_service.mock.ts b/src/core/public/injected_metadata/injected_metadata_service.mock.ts index 9e1d5aeec7ff4..9dfe019116669 100644 --- a/src/core/public/injected_metadata/injected_metadata_service.mock.ts +++ b/src/core/public/injected_metadata/injected_metadata_service.mock.ts @@ -23,7 +23,6 @@ const createSetupContractMock = () => { getBasePath: jest.fn(), getKibanaVersion: jest.fn(), getKibanaBranch: jest.fn(), - getCapabilities: jest.fn(), getCspConfig: jest.fn(), getLegacyMode: jest.fn(), getLegacyMetadata: jest.fn(), @@ -32,7 +31,6 @@ const createSetupContractMock = () => { getInjectedVars: jest.fn(), getKibanaBuildNumber: jest.fn(), }; - setupContract.getCapabilities.mockReturnValue({} as any); setupContract.getCspConfig.mockReturnValue({ warnLegacyBrowsers: true }); setupContract.getKibanaVersion.mockReturnValue('kibanaVersion'); setupContract.getLegacyMode.mockReturnValue(true); diff --git a/src/core/public/injected_metadata/injected_metadata_service.ts b/src/core/public/injected_metadata/injected_metadata_service.ts index 002f83d9feac4..0bde1b68e1876 100644 --- a/src/core/public/injected_metadata/injected_metadata_service.ts +++ b/src/core/public/injected_metadata/injected_metadata_service.ts @@ -26,7 +26,6 @@ import { UserProvidedValues, } from '../../server/types'; import { deepFreeze } from '../../utils/'; -import { Capabilities } from '..'; /** @public */ export interface LegacyNavLink { @@ -64,7 +63,6 @@ export interface InjectedMetadataParams { packageInfo: Readonly; }; uiPlugins: InjectedPluginMetadata[]; - capabilities: Capabilities; legacyMode: boolean; legacyMetadata: { app: unknown; @@ -114,10 +112,6 @@ export class InjectedMetadataService { return this.state.version; }, - getCapabilities: () => { - return this.state.capabilities; - }, - getCspConfig: () => { return this.state.csp; }, @@ -163,7 +157,6 @@ export interface InjectedMetadataSetup { getKibanaBuildNumber: () => number; getKibanaBranch: () => string; getKibanaVersion: () => string; - getCapabilities: () => Capabilities; getCspConfig: () => { warnLegacyBrowsers: boolean; }; diff --git a/src/core/server/capabilities/capabilities_service.ts b/src/core/server/capabilities/capabilities_service.ts index 533ab8e8a40c8..13668469aa292 100644 --- a/src/core/server/capabilities/capabilities_service.ts +++ b/src/core/server/capabilities/capabilities_service.ts @@ -17,6 +17,7 @@ * under the License. */ +import { schema } from '@kbn/config-schema'; import { Capabilities, CapabilitiesProvider, CapabilitiesSwitcher } from './types'; import { CoreContext } from '../core_context'; import { Logger } from '../logging'; @@ -86,14 +87,17 @@ export class CapabilitiesService { router.post( { path: '', - validate: false, + validate: { + body: schema.object({ + applications: schema.arrayOf(schema.string()), + }), + }, }, async (ctx, req, res) => { + // const { applications } = req.body; const capabilities = await this.resolver(req); return res.ok({ - body: { - capabilities, - }, + body: capabilities, }); } ); diff --git a/src/legacy/server/capabilities/capabilities_mixin.test.ts b/src/legacy/server/capabilities/capabilities_mixin.test.ts index 49d8294d4aef6..bd6a6c5bff577 100644 --- a/src/legacy/server/capabilities/capabilities_mixin.test.ts +++ b/src/legacy/server/capabilities/capabilities_mixin.test.ts @@ -48,17 +48,6 @@ describe('capabilitiesMixin', () => { registerMock = jest.fn(); }); - it('exposes request#getCapabilities for retrieving legacy capabilities', async () => { - const kbnServer = getKbnServer(); - jest.spyOn(server, 'decorate'); - await capabilitiesMixin(kbnServer, server); - expect(server.decorate).toHaveBeenCalledWith( - 'request', - 'getCapabilities', - expect.any(Function) - ); - }); - it('calls capabilities#registerCapabilitiesProvider for each legacy plugin specs', async () => { const getPluginSpec = (provider: () => any) => ({ getUiCapabilitiesProvider: () => provider, diff --git a/src/legacy/server/capabilities/capabilities_mixin.ts b/src/legacy/server/capabilities/capabilities_mixin.ts index 8a680f29d0599..8c146d596992c 100644 --- a/src/legacy/server/capabilities/capabilities_mixin.ts +++ b/src/legacy/server/capabilities/capabilities_mixin.ts @@ -18,7 +18,6 @@ */ import { Server } from 'hapi'; -import { KibanaRequest } from '../../../core/server'; import KbnServer from '../kbn_server'; export async function capabilitiesMixin(kbnServer: KbnServer, server: Server) { @@ -53,10 +52,4 @@ export async function capabilitiesMixin(kbnServer: KbnServer, server: Server) { kbnServer.afterPluginsInit(async () => { await registerLegacyCapabilities(); }); - - server.decorate('request', 'getCapabilities', function() { - return kbnServer.newPlatform.start.core.capabilities.resolveCapabilities( - KibanaRequest.from(this) - ); - }); } diff --git a/src/legacy/server/kbn_server.d.ts b/src/legacy/server/kbn_server.d.ts index 37db03688a5c8..87e21f286bcc0 100644 --- a/src/legacy/server/kbn_server.d.ts +++ b/src/legacy/server/kbn_server.d.ts @@ -89,7 +89,6 @@ declare module 'hapi' { getBasePath(): string; getDefaultRoute(): Promise; getUiSettingsService(): IUiSettingsClient; - getCapabilities(): Promise; } interface ResponseToolkit { diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js index 763167c6b5ccf..931f8b184421c 100644 --- a/src/legacy/ui/ui_render/ui_render_mixin.js +++ b/src/legacy/ui/ui_render/ui_render_mixin.js @@ -279,8 +279,6 @@ export function uiRenderMixin(kbnServer, server, config) { uiPlugins, legacyMetadata, - - capabilities: await request.getCapabilities(), }, }); From bdd07d093c9a7259d42105f6d73a6bfb6b5e1ffc Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Mon, 25 Nov 2019 10:36:42 +0100 Subject: [PATCH 10/34] use applications sent from client instead of server-registered navLinks --- .../capabilities/capabilities_service.tsx | 1 - .../capabilities/capabilities_service.test.ts | 47 +++++++++++++++++++ .../capabilities/capabilities_service.ts | 10 ++-- .../capabilities_service.test.ts | 29 ++++-------- .../capabilities/resolve_capabilities.test.ts | 16 +++---- .../capabilities/resolve_capabilities.ts | 27 +++++++++-- src/core/server/http/http_service.mock.ts | 7 +-- src/core/server/http/router/router.mock.ts | 37 +++++++++++++++ .../capabilities/capabilities_mixin.test.ts | 14 ------ .../server/capabilities/capabilities_mixin.ts | 11 ----- 10 files changed, 132 insertions(+), 67 deletions(-) create mode 100644 src/core/server/capabilities/capabilities_service.test.ts create mode 100644 src/core/server/http/router/router.mock.ts diff --git a/src/core/public/application/capabilities/capabilities_service.tsx b/src/core/public/application/capabilities/capabilities_service.tsx index 79cf905935246..8f48bbbc2f858 100644 --- a/src/core/public/application/capabilities/capabilities_service.tsx +++ b/src/core/public/application/capabilities/capabilities_service.tsx @@ -64,7 +64,6 @@ export interface CapabilitiesStart { export class CapabilitiesService { public async start({ apps, legacyApps, http }: StartDeps): Promise { const capabilities = await this.fetchCapabilities(http, [...apps.keys(), ...legacyApps.keys()]); - // console.log('response:', capabilities); const availableApps = new Map( [...apps].filter( diff --git a/src/core/server/capabilities/capabilities_service.test.ts b/src/core/server/capabilities/capabilities_service.test.ts new file mode 100644 index 0000000000000..7f64c698564b5 --- /dev/null +++ b/src/core/server/capabilities/capabilities_service.test.ts @@ -0,0 +1,47 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { httpServiceMock, HttpServiceSetupMock } from '../http/http_service.mock'; +import { mockRouter, RouterMock } from '../http/router/router.mock'; +import { CapabilitiesService } from './capabilities_service'; +import { mockCoreContext } from '../core_context.mock'; + +describe('CapabilitiesService', () => { + let http: HttpServiceSetupMock; + let service: CapabilitiesService; + let router: RouterMock; + + beforeEach(() => { + http = httpServiceMock.createSetupContract(); + router = mockRouter.create(); + http.createRouter.mockReturnValue(router); + service = new CapabilitiesService(mockCoreContext.create()); + }); + + describe('#setup()', () => { + beforeEach(() => { + service.setup({ http }); + }); + + it('registers the capabilities route', async () => { + expect(http.createRouter).toHaveBeenCalledWith('/core/capabilities'); + expect(router.post).toHaveBeenCalledWith(expect.any(Object), expect.any(Function)); + }); + }); +}); diff --git a/src/core/server/capabilities/capabilities_service.ts b/src/core/server/capabilities/capabilities_service.ts index 13668469aa292..4a2eebb042951 100644 --- a/src/core/server/capabilities/capabilities_service.ts +++ b/src/core/server/capabilities/capabilities_service.ts @@ -48,11 +48,11 @@ export class CapabilitiesService { private readonly logger: Logger; private readonly capabilitiesProviders: CapabilitiesProvider[] = []; private readonly capabilitiesSwitchers: CapabilitiesSwitcher[] = []; - private readonly resolver: CapabilitiesResolver; + private readonly resolveCapabilities: CapabilitiesResolver; constructor(core: CoreContext) { this.logger = core.logger.get('capabilities-service'); - this.resolver = getCapabilitiesResolver( + this.resolveCapabilities = getCapabilitiesResolver( () => mergeCapabilities( defaultCapabilities, @@ -78,7 +78,7 @@ export class CapabilitiesService { public start(): CapabilitiesStart { return { - resolveCapabilities: request => this.resolver(request), + resolveCapabilities: request => this.resolveCapabilities(request, []), }; } @@ -94,8 +94,8 @@ export class CapabilitiesService { }, }, async (ctx, req, res) => { - // const { applications } = req.body; - const capabilities = await this.resolver(req); + const { applications } = req.body; + const capabilities = await this.resolveCapabilities(req, applications); return res.ok({ body: capabilities, }); diff --git a/src/core/server/capabilities/integration_tests/capabilities_service.test.ts b/src/core/server/capabilities/integration_tests/capabilities_service.test.ts index c921bb4082c2e..e8f3928f2473d 100644 --- a/src/core/server/capabilities/integration_tests/capabilities_service.test.ts +++ b/src/core/server/capabilities/integration_tests/capabilities_service.test.ts @@ -85,25 +85,19 @@ describe('CapabilitiesService', () => { it('is exposed', async () => { const result = await supertest(httpSetup.server.listener) .post('/core/capabilities') - .send({}) + .send({ applications: [] }) .expect(200); expect(result.body).toMatchInlineSnapshot(` Object { - "capabilities": Object { - "catalogue": Object {}, - "management": Object {}, - "navLinks": Object {}, - }, + "catalogue": Object {}, + "management": Object {}, + "navLinks": Object {}, } `); }); it('uses the service capabilities providers', async () => { serviceSetup.registerCapabilitiesProvider(() => ({ - navLinks: { - app: true, - otherApp: true, - }, catalogue: { something: true, }, @@ -111,20 +105,15 @@ describe('CapabilitiesService', () => { const result = await supertest(httpSetup.server.listener) .post('/core/capabilities') - .send({}) + .send({ applications: [] }) .expect(200); expect(result.body).toMatchInlineSnapshot(` Object { - "capabilities": Object { - "catalogue": Object { - "something": true, - }, - "management": Object {}, - "navLinks": Object { - "app": true, - "otherApp": true, - }, + "catalogue": Object { + "something": true, }, + "management": Object {}, + "navLinks": Object {}, } `); }); diff --git a/src/core/server/capabilities/resolve_capabilities.test.ts b/src/core/server/capabilities/resolve_capabilities.test.ts index eee8182a1ba99..2a2e603a8582c 100644 --- a/src/core/server/capabilities/resolve_capabilities.test.ts +++ b/src/core/server/capabilities/resolve_capabilities.test.ts @@ -36,34 +36,34 @@ describe('resolveCapabilities', () => { }); it('should returns the initial capabilities if no switcher are used', async () => { - const result = await resolveCapabilities(defaultCaps, [], request); + const result = await resolveCapabilities(defaultCaps, [], request, []); expect(result).toEqual(defaultCaps); }); it('should apply the switcher to the capabilities ', async () => { const caps = { ...defaultCaps, - navLinks: { + catalogue: { A: true, B: true, }, }; const switcher = (req: KibanaRequest, capabilities: Capabilities) => ({ ...capabilities, - navLinks: { - ...capabilities.navLinks, + catalogue: { + ...capabilities.catalogue, A: false, }, }); - const result = await resolveCapabilities(caps, [switcher], request); + const result = await resolveCapabilities(caps, [switcher], request, []); expect(result).toMatchInlineSnapshot(` Object { - "catalogue": Object {}, - "management": Object {}, - "navLinks": Object { + "catalogue": Object { "A": false, "B": true, }, + "management": Object {}, + "navLinks": Object {}, } `); }); diff --git a/src/core/server/capabilities/resolve_capabilities.ts b/src/core/server/capabilities/resolve_capabilities.ts index a755d074f67ac..15a674d4b2f82 100644 --- a/src/core/server/capabilities/resolve_capabilities.ts +++ b/src/core/server/capabilities/resolve_capabilities.ts @@ -20,21 +20,38 @@ import { Capabilities, CapabilitiesSwitcher } from './types'; import { KibanaRequest } from '../http'; -export type CapabilitiesResolver = (request: KibanaRequest) => Promise; +export type CapabilitiesResolver = ( + request: KibanaRequest, + applications: string[] +) => Promise; export const getCapabilitiesResolver = ( capabilities: () => Capabilities, switchers: () => CapabilitiesSwitcher[] -): CapabilitiesResolver => async (request: KibanaRequest): Promise => { - return resolveCapabilities(capabilities(), switchers(), request); +): CapabilitiesResolver => async ( + request: KibanaRequest, + applications: string[] +): Promise => { + return resolveCapabilities(capabilities(), switchers(), request, applications); }; export const resolveCapabilities = async ( capabilities: Capabilities, switchers: CapabilitiesSwitcher[], - request: KibanaRequest + request: KibanaRequest, + applications: string[] ): Promise => { + const mergedCaps = { + ...capabilities, + navLinks: applications.reduce( + (acc, app) => ({ + ...acc, + [app]: true, + }), + {} + ), + }; return switchers.reduce(async (caps, switcher) => { return switcher(request, await caps); - }, Promise.resolve(capabilities)); + }, Promise.resolve(mergedCaps)); }; diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index e9a2571382edc..0d3f3d1b9696b 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -18,6 +18,7 @@ */ import { Server } from 'hapi'; +import { mockRouter } from './router/router.mock'; import { InternalHttpServiceSetup } from './types'; import { HttpService } from './http_service'; import { OnPreAuthToolkit } from './lifecycle/on_pre_auth'; @@ -26,7 +27,7 @@ import { sessionStorageMock } from './cookie_session_storage.mocks'; import { IRouter } from './router'; import { OnPostAuthToolkit } from './lifecycle/on_post_auth'; -type ServiceSetupMockType = jest.Mocked & { +export type HttpServiceSetupMock = jest.Mocked & { basePath: jest.Mocked; }; @@ -49,7 +50,7 @@ const createRouterMock = (): jest.Mocked => ({ }); const createSetupContractMock = () => { - const setupContract: ServiceSetupMockType = { + const setupContract: HttpServiceSetupMock = { // we can mock other hapi server methods when we need it server: ({ route: jest.fn(), @@ -61,7 +62,7 @@ const createSetupContractMock = () => { registerAuth: jest.fn(), registerOnPostAuth: jest.fn(), registerRouteHandlerContext: jest.fn(), - createRouter: jest.fn(), + createRouter: jest.fn().mockImplementation(() => mockRouter.create({})), basePath: createBasePathMock(), auth: { get: jest.fn(), diff --git a/src/core/server/http/router/router.mock.ts b/src/core/server/http/router/router.mock.ts new file mode 100644 index 0000000000000..3081ccf8470c7 --- /dev/null +++ b/src/core/server/http/router/router.mock.ts @@ -0,0 +1,37 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IRouter } from './router'; + +export type RouterMock = DeeplyMockedKeys; + +function create({ routerPath = '' }: { routerPath?: string } = {}): RouterMock { + return { + routerPath, + get: jest.fn(), + post: jest.fn(), + delete: jest.fn(), + put: jest.fn(), + getRoutes: jest.fn(), + }; +} + +export const mockRouter = { + create, +}; diff --git a/src/legacy/server/capabilities/capabilities_mixin.test.ts b/src/legacy/server/capabilities/capabilities_mixin.test.ts index bd6a6c5bff577..ea4d60a85eb88 100644 --- a/src/legacy/server/capabilities/capabilities_mixin.test.ts +++ b/src/legacy/server/capabilities/capabilities_mixin.test.ts @@ -62,18 +62,4 @@ describe('capabilitiesMixin', () => { expect(registerMock.mock.calls[0][0]()).toEqual(capaA); expect(registerMock.mock.calls[1][0]()).toEqual(capaB); }); - - it('calls capabilities#registerCapabilitiesProvider for navLinks', async () => { - const kbnServer = getKbnServer(); - server.getUiNavLinks = () => [{ _id: 'A' }, { _id: 'B' }]; - await capabilitiesMixin(kbnServer, server); - - expect(registerMock).toHaveBeenCalledTimes(1); - expect(registerMock.mock.calls[0][0]()).toEqual({ - navLinks: { - A: true, - B: true, - }, - }); - }); }); diff --git a/src/legacy/server/capabilities/capabilities_mixin.ts b/src/legacy/server/capabilities/capabilities_mixin.ts index 8c146d596992c..c833f6714f807 100644 --- a/src/legacy/server/capabilities/capabilities_mixin.ts +++ b/src/legacy/server/capabilities/capabilities_mixin.ts @@ -28,17 +28,6 @@ export async function capabilitiesMixin(kbnServer: KbnServer, server: Server) { .filter(provider => !!provider) .map(provider => provider(server)) ); - // Get legacy nav links - const navLinks = server.getUiNavLinks().reduce( - (acc, spec) => ({ - ...acc, - [spec._id]: true, - }), - {} as Record - ); - if (Object.keys(navLinks).length) { - capabilitiesList.push({ navLinks }); - } capabilitiesList.forEach(capabilities => { kbnServer.newPlatform.setup.core.capabilities.registerCapabilitiesProvider( From 6650273ffae42e16618776dbd1d792256b86d54a Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Mon, 25 Nov 2019 11:51:43 +0100 Subject: [PATCH 11/34] disable authRequired for capabilities route --- .../public/application/capabilities/capabilities_service.tsx | 2 +- src/core/server/capabilities/capabilities_service.test.ts | 2 +- src/core/server/capabilities/capabilities_service.ts | 5 ++++- .../integration_tests/capabilities_service.test.ts | 4 ++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/core/public/application/capabilities/capabilities_service.tsx b/src/core/public/application/capabilities/capabilities_service.tsx index 8f48bbbc2f858..491e784895b46 100644 --- a/src/core/public/application/capabilities/capabilities_service.tsx +++ b/src/core/public/application/capabilities/capabilities_service.tsx @@ -88,7 +88,7 @@ export class CapabilitiesService { private async fetchCapabilities(http: HttpStart, appIds: string[]): Promise { return deepFreeze( - await http.post('/core/capabilities', { + await http.post('/api/core/capabilities', { body: JSON.stringify({ applications: appIds, }), diff --git a/src/core/server/capabilities/capabilities_service.test.ts b/src/core/server/capabilities/capabilities_service.test.ts index 7f64c698564b5..dcd98090f2d27 100644 --- a/src/core/server/capabilities/capabilities_service.test.ts +++ b/src/core/server/capabilities/capabilities_service.test.ts @@ -40,7 +40,7 @@ describe('CapabilitiesService', () => { }); it('registers the capabilities route', async () => { - expect(http.createRouter).toHaveBeenCalledWith('/core/capabilities'); + expect(http.createRouter).toHaveBeenCalledWith('/api/core/capabilities'); expect(router.post).toHaveBeenCalledWith(expect.any(Object), expect.any(Function)); }); }); diff --git a/src/core/server/capabilities/capabilities_service.ts b/src/core/server/capabilities/capabilities_service.ts index 4a2eebb042951..bf0d4378ef160 100644 --- a/src/core/server/capabilities/capabilities_service.ts +++ b/src/core/server/capabilities/capabilities_service.ts @@ -83,10 +83,13 @@ export class CapabilitiesService { } private setupCapabilitiesRoute(http: InternalHttpServiceSetup) { - const router = http.createRouter('/core/capabilities'); + const router = http.createRouter('/api/core/capabilities'); router.post( { path: '', + options: { + authRequired: false, + }, validate: { body: schema.object({ applications: schema.arrayOf(schema.string()), diff --git a/src/core/server/capabilities/integration_tests/capabilities_service.test.ts b/src/core/server/capabilities/integration_tests/capabilities_service.test.ts index e8f3928f2473d..73382e82ce8ee 100644 --- a/src/core/server/capabilities/integration_tests/capabilities_service.test.ts +++ b/src/core/server/capabilities/integration_tests/capabilities_service.test.ts @@ -81,7 +81,7 @@ describe('CapabilitiesService', () => { await server.stop(); }); - describe('/core/capabilities route', () => { + describe('/api/core/capabilities route', () => { it('is exposed', async () => { const result = await supertest(httpSetup.server.listener) .post('/core/capabilities') @@ -104,7 +104,7 @@ describe('CapabilitiesService', () => { })); const result = await supertest(httpSetup.server.listener) - .post('/core/capabilities') + .post('/api/core/capabilities') .send({ applications: [] }) .expect(200); expect(result.body).toMatchInlineSnapshot(` From dd160feb31a6bdec98d99588c5213e014428093a Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Mon, 25 Nov 2019 15:22:13 +0100 Subject: [PATCH 12/34] (temp) exposes two endpoints for capabilities --- .../capabilities/capabilities_service.tsx | 27 ++++++++++++++----- .../capabilities/capabilities_service.ts | 20 ++++++++++++++ 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/core/public/application/capabilities/capabilities_service.tsx b/src/core/public/application/capabilities/capabilities_service.tsx index 491e784895b46..80cc24d021fff 100644 --- a/src/core/public/application/capabilities/capabilities_service.tsx +++ b/src/core/public/application/capabilities/capabilities_service.tsx @@ -87,12 +87,25 @@ export class CapabilitiesService { } private async fetchCapabilities(http: HttpStart, appIds: string[]): Promise { - return deepFreeze( - await http.post('/api/core/capabilities', { - body: JSON.stringify({ - applications: appIds, - }), - }) - ); + let capabilities: Capabilities = { + navLinks: {}, + catalogue: {}, + management: {}, + }; + const payload = JSON.stringify({ + applications: appIds, + }); + try { + capabilities = await http.post('/api/core/capabilities', { + body: payload, + }); + } catch (e) { + if (e?.body?.statusCode === 401) { + capabilities = await http.post('/api/core/capabilities/fallback', { + body: payload, + }); + } + } + return deepFreeze(capabilities); } } diff --git a/src/core/server/capabilities/capabilities_service.ts b/src/core/server/capabilities/capabilities_service.ts index bf0d4378ef160..e9856463ee0cc 100644 --- a/src/core/server/capabilities/capabilities_service.ts +++ b/src/core/server/capabilities/capabilities_service.ts @@ -87,6 +87,26 @@ export class CapabilitiesService { router.post( { path: '', + options: { + authRequired: true, + }, + validate: { + body: schema.object({ + applications: schema.arrayOf(schema.string()), + }), + }, + }, + async (ctx, req, res) => { + const { applications } = req.body; + const capabilities = await this.resolveCapabilities(req, applications); + return res.ok({ + body: capabilities, + }); + } + ); + router.post( + { + path: '/fallback', options: { authRequired: false, }, From ad99a9670f6743e2ab58f53be2af6b5c9963712a Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Mon, 25 Nov 2019 21:40:34 +0100 Subject: [PATCH 13/34] Add fetch-mock on capabilities call for karma tests --- src/legacy/core_plugins/tests_bundle/tests_entry_template.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/legacy/core_plugins/tests_bundle/tests_entry_template.js b/src/legacy/core_plugins/tests_bundle/tests_entry_template.js index 17632a2c0a405..347968ecb80c5 100644 --- a/src/legacy/core_plugins/tests_bundle/tests_entry_template.js +++ b/src/legacy/core_plugins/tests_bundle/tests_entry_template.js @@ -70,9 +70,9 @@ const uiCapabilities = { // Mock fetch for CoreSystem calls. fetchMock.config.fallbackToNetwork = true; -fetchMock.post(/\\/api\\/capabilities/, { +fetchMock.post(/\\/api\\/core\\/capabilities/, { status: 200, - body: JSON.stringify({ capabilities: uiCapabilities }), + body: JSON.stringify(uiCapabilities), headers: { 'Content-Type': 'application/json' }, }); From 90234d734225a47ebe1561aa6012e066ce6c8479 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Mon, 25 Nov 2019 22:31:13 +0100 Subject: [PATCH 14/34] adapt xpack Capabilities test - first attempt --- .../common/services/ui_capabilities.ts | 56 +++++++++---------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/x-pack/test/ui_capabilities/common/services/ui_capabilities.ts b/x-pack/test/ui_capabilities/common/services/ui_capabilities.ts index 56969879b6322..4af7d81e5a7b4 100644 --- a/x-pack/test/ui_capabilities/common/services/ui_capabilities.ts +++ b/x-pack/test/ui_capabilities/common/services/ui_capabilities.ts @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ import axios, { AxiosInstance } from 'axios'; -import cheerio from 'cheerio'; import { UICapabilities } from 'ui/capabilities'; import { format as formatUrl } from 'url'; import util from 'util'; import { ToolingLog } from '@kbn/dev-utils'; import { FtrProviderContext } from '../ftr_provider_context'; +import { FeaturesService, FeaturesProvider } from './features'; export interface BasicCredentials { username: string; @@ -30,8 +30,9 @@ interface GetUICapabilitiesResult { export class UICapabilitiesService { private readonly log: ToolingLog; private readonly axios: AxiosInstance; + private readonly featureService: FeaturesService; - constructor(url: string, log: ToolingLog) { + constructor(url: string, log: ToolingLog, featureService: FeaturesService) { this.log = log; this.axios = axios.create({ headers: { 'kbn-xsrf': 'x-pack/ftr/services/ui_capabilities' }, @@ -39,6 +40,7 @@ export class UICapabilitiesService { maxRedirects: 0, validateStatus: () => true, // we'll handle our own statusCodes and throw informative errors }); + this.featureService = featureService; } public async get({ @@ -48,8 +50,15 @@ export class UICapabilitiesService { credentials?: BasicCredentials; spaceId?: string; }): Promise { + const features = await this.featureService.get(); + const applications = Object.values(features) + .map(feature => feature.navLinkId) + .filter(link => !!link); + const spaceUrlPrefix = spaceId ? `/s/${spaceId}` : ''; - this.log.debug(`requesting ${spaceUrlPrefix}/app/kibana to parse the uiCapabilities`); + this.log.debug( + `requesting ${spaceUrlPrefix}/api/core/capabilities to parse the uiCapabilities` + ); const requestHeaders = credentials ? { Authorization: `Basic ${Buffer.from( @@ -57,9 +66,13 @@ export class UICapabilitiesService { ).toString('base64')}`, } : {}; - const response = await this.axios.get(`${spaceUrlPrefix}/app/kibana`, { - headers: requestHeaders, - }); + const response = await this.axios.post( + `${spaceUrlPrefix}/api/core/capabilities`, + { applications: [...applications, 'kibana:management'] }, + { + headers: requestHeaders, + } + ); if (response.status === 302 && response.headers.location === '/spaces/space_selector') { return { @@ -83,35 +96,20 @@ export class UICapabilitiesService { ); } - const dom = cheerio.load(response.data.toString()); - const element = dom('kbn-injected-metadata'); - if (!element) { - throw new Error('Unable to find "kbn-injected-metadata" element'); - } - - const dataAttrJson = element.attr('data'); - - try { - const dataAttr = JSON.parse(dataAttrJson); - return { - success: true, - value: dataAttr.capabilities as UICapabilities, - }; - } catch (err) { - throw new Error( - `Unable to parse JSON from the kbn-injected-metadata data attribute: ${dataAttrJson}` - ); - } + return { + success: true, + value: response.data, + }; } } -export function UICapabilitiesProvider({ getService }: FtrProviderContext) { - const log = getService('log'); - const config = getService('config'); +export function UICapabilitiesProvider(context: FtrProviderContext) { + const log = context.getService('log'); + const config = context.getService('config'); const noAuthUrl = formatUrl({ ...config.get('servers.kibana'), auth: undefined, }); - return new UICapabilitiesService(noAuthUrl, log); + return new UICapabilitiesService(noAuthUrl, log, FeaturesProvider(context)); } From 3a81fb6ca6b6e7d6ee4dbeb83462b2f6f4b5a363 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Tue, 26 Nov 2019 00:16:20 +0100 Subject: [PATCH 15/34] adapt x-pack ui_capabilities test --- .../capabilities_service.test.ts | 2 +- .../security_and_spaces/tests/catalogue.ts | 25 +++++++------------ .../security_and_spaces/tests/foo.ts | 25 +++++++------------ .../security_and_spaces/tests/nav_links.ts | 15 +++-------- .../security_only/tests/catalogue.ts | 12 ++++----- .../security_only/tests/foo.ts | 15 ++++++----- .../security_only/tests/nav_links.ts | 10 +++----- 7 files changed, 42 insertions(+), 62 deletions(-) diff --git a/src/core/server/capabilities/integration_tests/capabilities_service.test.ts b/src/core/server/capabilities/integration_tests/capabilities_service.test.ts index 73382e82ce8ee..8d0e703723ed0 100644 --- a/src/core/server/capabilities/integration_tests/capabilities_service.test.ts +++ b/src/core/server/capabilities/integration_tests/capabilities_service.test.ts @@ -84,7 +84,7 @@ describe('CapabilitiesService', () => { describe('/api/core/capabilities route', () => { it('is exposed', async () => { const result = await supertest(httpSetup.server.listener) - .post('/core/capabilities') + .post('/api/core/capabilities') .send({ applications: [] }) .expect(200); expect(result.body).toMatchInlineSnapshot(` diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts index 6b15d1bff4209..d7d15ddfeec9c 100644 --- a/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts +++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts @@ -7,10 +7,7 @@ import expect from '@kbn/expect'; import { mapValues } from 'lodash'; import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { - GetUICapabilitiesFailureReason, - UICapabilitiesService, -} from '../../common/services/ui_capabilities'; +import { UICapabilitiesService } from '../../common/services/ui_capabilities'; import { UserAtSpaceScenarios } from '../scenarios'; export default function catalogueTests({ getService }: FtrProviderContext) { @@ -58,15 +55,7 @@ export default function catalogueTests({ getService }: FtrProviderContext) { case 'dual_privileges_all at nothing_space': case 'dual_privileges_read at nothing_space': case 'nothing_space_all at nothing_space': - case 'nothing_space_read at nothing_space': { - expect(uiCapabilities.success).to.be(true); - expect(uiCapabilities.value).to.have.property('catalogue'); - // everything is disabled - const expected = mapValues(uiCapabilities.value!.catalogue, () => false); - expect(uiCapabilities.value!.catalogue).to.eql(expected); - break; - } - // if we don't have access at the space itself, security interceptor responds with 404. + case 'nothing_space_read at nothing_space': case 'no_kibana_privileges at everything_space': case 'no_kibana_privileges at nothing_space': case 'legacy_all at everything_space': @@ -74,10 +63,14 @@ export default function catalogueTests({ getService }: FtrProviderContext) { case 'everything_space_all at nothing_space': case 'everything_space_read at nothing_space': case 'nothing_space_all at everything_space': - case 'nothing_space_read at everything_space': - expect(uiCapabilities.success).to.be(false); - expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); + case 'nothing_space_read at everything_space': { + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('catalogue'); + // everything is disabled + const expected = mapValues(uiCapabilities.value!.catalogue, () => false); + expect(uiCapabilities.value!.catalogue).to.eql(expected); break; + } default: throw new UnreachableError(scenario); } diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/foo.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/foo.ts index ad4c3582d468f..d9cec21a41bc8 100644 --- a/x-pack/test/ui_capabilities/security_and_spaces/tests/foo.ts +++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/foo.ts @@ -6,10 +6,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { - GetUICapabilitiesFailureReason, - UICapabilitiesService, -} from '../../common/services/ui_capabilities'; +import { UICapabilitiesService } from '../../common/services/ui_capabilities'; import { UserAtSpaceScenarios } from '../scenarios'; export default function fooTests({ getService }: FtrProviderContext) { @@ -61,16 +58,6 @@ export default function fooTests({ getService }: FtrProviderContext) { case 'dual_privileges_read at nothing_space': case 'nothing_space_all at nothing_space': case 'nothing_space_read at nothing_space': - expect(uiCapabilities.success).to.be(true); - expect(uiCapabilities.value).to.have.property('foo'); - expect(uiCapabilities.value!.foo).to.eql({ - create: false, - edit: false, - delete: false, - show: false, - }); - break; - // if we don't have access at the space itself, security interceptor responds with 404. case 'no_kibana_privileges at everything_space': case 'no_kibana_privileges at nothing_space': case 'legacy_all at everything_space': @@ -79,8 +66,14 @@ export default function fooTests({ getService }: FtrProviderContext) { case 'everything_space_read at nothing_space': case 'nothing_space_all at everything_space': case 'nothing_space_read at everything_space': - expect(uiCapabilities.success).to.be(false); - expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('foo'); + expect(uiCapabilities.value!.foo).to.eql({ + create: false, + edit: false, + delete: false, + show: false, + }); break; default: throw new UnreachableError(scenario); diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts index e9d0cf28e96ec..beb781fdfe8b6 100644 --- a/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts +++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts @@ -8,10 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { NavLinksBuilder } from '../../common/nav_links_builder'; import { FeaturesService } from '../../common/services'; -import { - GetUICapabilitiesFailureReason, - UICapabilitiesService, -} from '../../common/services/ui_capabilities'; +import { UICapabilitiesService } from '../../common/services/ui_capabilities'; import { UserAtSpaceScenarios } from '../scenarios'; export default function navLinksTests({ getService }: FtrProviderContext) { @@ -58,11 +55,6 @@ export default function navLinksTests({ getService }: FtrProviderContext) { case 'global_read at nothing_space': case 'nothing_space_all at nothing_space': case 'nothing_space_read at nothing_space': - expect(uiCapabilities.success).to.be(true); - expect(uiCapabilities.value).to.have.property('navLinks'); - expect(uiCapabilities.value!.navLinks).to.eql(navLinksBuilder.only('management')); - break; - // if we don't have access at the space itself, security interceptor responds with 404. case 'no_kibana_privileges at everything_space': case 'no_kibana_privileges at nothing_space': case 'legacy_all at everything_space': @@ -71,8 +63,9 @@ export default function navLinksTests({ getService }: FtrProviderContext) { case 'everything_space_read at nothing_space': case 'nothing_space_all at everything_space': case 'nothing_space_read at everything_space': - expect(uiCapabilities.success).to.be(false); - expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('navLinks'); + expect(uiCapabilities.value!.navLinks).to.eql(navLinksBuilder.only('management')); break; default: throw new UnreachableError(scenario); diff --git a/x-pack/test/ui_capabilities/security_only/tests/catalogue.ts b/x-pack/test/ui_capabilities/security_only/tests/catalogue.ts index 9a46820e87f3a..232fd19c5c5b8 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/catalogue.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/catalogue.ts @@ -7,10 +7,7 @@ import expect from '@kbn/expect'; import { mapValues } from 'lodash'; import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { - GetUICapabilitiesFailureReason, - UICapabilitiesService, -} from '../../common/services/ui_capabilities'; +import { UICapabilitiesService } from '../../common/services/ui_capabilities'; import { UserScenarios } from '../scenarios'; export default function catalogueTests({ getService }: FtrProviderContext) { @@ -63,8 +60,11 @@ export default function catalogueTests({ getService }: FtrProviderContext) { // these users have no access to even get the ui capabilities case 'legacy_all': case 'no_kibana_privileges': - expect(uiCapabilities.success).to.be(false); - expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('catalogue'); + // only foo is enabled + const expected = mapValues(uiCapabilities.value!.catalogue, () => false); + expect(uiCapabilities.value!.catalogue).to.eql(expected); break; default: throw new UnreachableError(scenario); diff --git a/x-pack/test/ui_capabilities/security_only/tests/foo.ts b/x-pack/test/ui_capabilities/security_only/tests/foo.ts index a2a663921e6ea..f5f9ae3d5020e 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/foo.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/foo.ts @@ -6,10 +6,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { - GetUICapabilitiesFailureReason, - UICapabilitiesService, -} from '../../common/services/ui_capabilities'; +import { UICapabilitiesService } from '../../common/services/ui_capabilities'; import { UserScenarios } from '../scenarios'; export default function fooTests({ getService }: FtrProviderContext) { @@ -55,8 +52,14 @@ export default function fooTests({ getService }: FtrProviderContext) { // these users have no access to even get the ui capabilities case 'legacy_all': case 'no_kibana_privileges': - expect(uiCapabilities.success).to.be(false); - expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('foo'); + expect(uiCapabilities.value!.foo).to.eql({ + create: false, + edit: false, + delete: false, + show: false, + }); break; // all other users can't do anything with Foo default: diff --git a/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts b/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts index f643753a5c37c..dd451bba3b538 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts @@ -8,10 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { NavLinksBuilder } from '../../common/nav_links_builder'; import { FeaturesService } from '../../common/services'; -import { - GetUICapabilitiesFailureReason, - UICapabilitiesService, -} from '../../common/services/ui_capabilities'; +import { UICapabilitiesService } from '../../common/services/ui_capabilities'; import { UserScenarios } from '../scenarios'; export default function navLinksTests({ getService }: FtrProviderContext) { @@ -59,8 +56,9 @@ export default function navLinksTests({ getService }: FtrProviderContext) { break; case 'legacy_all': case 'no_kibana_privileges': - expect(uiCapabilities.success).to.be(false); - expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('navLinks'); + expect(uiCapabilities.value!.navLinks).to.eql(navLinksBuilder.only('management')); break; default: throw new UnreachableError(scenario); From f5acc1f473697be7efa3af6d9a77ad659bfb0fd8 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Tue, 26 Nov 2019 08:30:27 +0100 Subject: [PATCH 16/34] add '/status' to the list of anonymous pages --- .../capabilities/capabilities_service.tsx | 23 ++++++------------- .../capabilities/capabilities_service.ts | 2 +- x-pack/plugins/security/public/plugin.ts | 1 + 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/core/public/application/capabilities/capabilities_service.tsx b/src/core/public/application/capabilities/capabilities_service.tsx index 80cc24d021fff..acbefaac3b22e 100644 --- a/src/core/public/application/capabilities/capabilities_service.tsx +++ b/src/core/public/application/capabilities/capabilities_service.tsx @@ -87,25 +87,16 @@ export class CapabilitiesService { } private async fetchCapabilities(http: HttpStart, appIds: string[]): Promise { - let capabilities: Capabilities = { - navLinks: {}, - catalogue: {}, - management: {}, - }; const payload = JSON.stringify({ applications: appIds, }); - try { - capabilities = await http.post('/api/core/capabilities', { - body: payload, - }); - } catch (e) { - if (e?.body?.statusCode === 401) { - capabilities = await http.post('/api/core/capabilities/fallback', { - body: payload, - }); - } - } + + const url = http.anonymousPaths.isAnonymous(window.location.pathname) + ? '/api/core/capabilities/defaults' + : '/api/core/capabilities'; + const capabilities = await http.post(url, { + body: payload, + }); return deepFreeze(capabilities); } } diff --git a/src/core/server/capabilities/capabilities_service.ts b/src/core/server/capabilities/capabilities_service.ts index e9856463ee0cc..b91f5714da101 100644 --- a/src/core/server/capabilities/capabilities_service.ts +++ b/src/core/server/capabilities/capabilities_service.ts @@ -106,7 +106,7 @@ export class CapabilitiesService { ); router.post( { - path: '/fallback', + path: '/defaults', options: { authRequired: false, }, diff --git a/x-pack/plugins/security/public/plugin.ts b/x-pack/plugins/security/public/plugin.ts index 55d125bf993ec..e0651e5c5f62b 100644 --- a/x-pack/plugins/security/public/plugin.ts +++ b/x-pack/plugins/security/public/plugin.ts @@ -19,6 +19,7 @@ export class SecurityPlugin implements Plugin Date: Tue, 26 Nov 2019 09:47:58 +0100 Subject: [PATCH 17/34] Add documentation on Capabilities APIs --- .../capabilities/capabilities_service.ts | 55 +++++++++++++++++++ src/core/server/capabilities/types.ts | 8 +++ 2 files changed, 63 insertions(+) diff --git a/src/core/server/capabilities/capabilities_service.ts b/src/core/server/capabilities/capabilities_service.ts index b91f5714da101..fb28255234770 100644 --- a/src/core/server/capabilities/capabilities_service.ts +++ b/src/core/server/capabilities/capabilities_service.ts @@ -25,12 +25,66 @@ import { InternalHttpServiceSetup, KibanaRequest } from '../http'; import { mergeCapabilities } from './merge_capabilities'; import { getCapabilitiesResolver, CapabilitiesResolver } from './resolve_capabilities'; +/** + * APIs to manage the {@link Capabilities} that will be used by the application. + * + * @public + */ export interface CapabilitiesSetup { + /** + * Register a {@link CapabilitiesProvider} to be used when resolving capabilities. + * + * @example + * ```ts + * // my-plugin/server/plugin.ts + * public setup(core: CoreSetup, deps: {}) { + * core.capabilities.registerCapabilitiesProvider(() => { + * return { + * catalogue: { + * myPlugin: true, + * }, + * myPlugin: { + * feature: true, + * }, + * } + * }) + * } + * ``` + */ registerCapabilitiesProvider(provider: CapabilitiesProvider): void; + + /** + * Register a {@link CapabilitiesSwitcher} to be used when resolving capabilities. + * + * @example + * ```ts + * // my-plugin/server/plugin.ts + * public setup(core: CoreSetup, deps: {}) { + * core.capabilities.registerCapabilitiesSwitcher((request, capabilities) => { + * if(myPluginApi.shouldRestrictBecauseOf(request)) { + * return myPluginApi.restrictCapabilities(capabilities); + * } + * return capabilities; + * }) + * } + * ``` + * + * @remarks + * A capabilities switcher can only change the state of existing capabilities. Adding or removing + * capabilities when invoking the switcher will raise an error. + */ registerCapabilitiesSwitcher(switcher: CapabilitiesSwitcher): void; } +/** + * APIs to access the application {@link Capabilities}. + * + * @public + */ export interface CapabilitiesStart { + /** + * Resolve the {@link Capabilities} to be used for given request + */ resolveCapabilities(request: KibanaRequest): Promise; } @@ -44,6 +98,7 @@ const defaultCapabilities: Capabilities = { catalogue: {}, }; +/** @internal */ export class CapabilitiesService { private readonly logger: Logger; private readonly capabilitiesProviders: CapabilitiesProvider[] = []; diff --git a/src/core/server/capabilities/types.ts b/src/core/server/capabilities/types.ts index 0cf686f7ebb26..98b18796fc1d8 100644 --- a/src/core/server/capabilities/types.ts +++ b/src/core/server/capabilities/types.ts @@ -42,8 +42,16 @@ export interface Capabilities { [key: string]: Record>; } +/** + * See {@link CapabilitiesSetup} + * @public + */ export type CapabilitiesProvider = () => Partial; +/** + * See {@link CapabilitiesSetup} + * @public + */ export type CapabilitiesSwitcher = ( request: KibanaRequest, uiCapabilities: Capabilities From 33848c1277d6040a42dde07b525144a3d9cfae66 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Tue, 26 Nov 2019 10:02:06 +0100 Subject: [PATCH 18/34] move Capabilities to core/types --- .../capabilities/capabilities_service.tsx | 24 +---------- src/core/server/capabilities/types.ts | 24 +---------- src/core/types/capabilities.ts | 41 +++++++++++++++++++ src/core/types/index.ts | 1 + 4 files changed, 46 insertions(+), 44 deletions(-) create mode 100644 src/core/types/capabilities.ts diff --git a/src/core/public/application/capabilities/capabilities_service.tsx b/src/core/public/application/capabilities/capabilities_service.tsx index acbefaac3b22e..e330a4b0326ae 100644 --- a/src/core/public/application/capabilities/capabilities_service.tsx +++ b/src/core/public/application/capabilities/capabilities_service.tsx @@ -17,6 +17,7 @@ * under the License. */ +import { Capabilities } from '../../../types/capabilities'; import { deepFreeze, RecursiveReadonly } from '../../../utils'; import { LegacyApp, App } from '../types'; import { HttpStart } from '../../http'; @@ -27,28 +28,7 @@ interface StartDeps { http: HttpStart; } -/** - * The read-only set of capabilities available for the current UI session. - * Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, - * and the boolean is a flag indicating if the capability is enabled or disabled. - * - * @public - */ -export interface Capabilities { - /** Navigation link capabilities. */ - navLinks: Record; - - /** Management section capabilities. */ - management: { - [sectionId: string]: Record; - }; - - /** Catalogue capabilities. Catalogue entries drive the visibility of the Kibana homepage options. */ - catalogue: Record; - - /** Custom capabilities, registered by plugins. */ - [key: string]: Record>; -} +export { Capabilities }; /** @internal */ export interface CapabilitiesStart { diff --git a/src/core/server/capabilities/types.ts b/src/core/server/capabilities/types.ts index 98b18796fc1d8..ca846185567a2 100644 --- a/src/core/server/capabilities/types.ts +++ b/src/core/server/capabilities/types.ts @@ -17,30 +17,10 @@ * under the License. */ +import { Capabilities } from '../../types/capabilities'; import { KibanaRequest } from '../http'; -/** - * The read-only set of capabilities available for the current UI session. - * Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, - * and the boolean is a flag indicating if the capability is enabled or disabled. - * - * @public - */ -export interface Capabilities { - /** Navigation link capabilities. */ - navLinks: Record; - - /** Management section capabilities. */ - management: { - [sectionId: string]: Record; - }; - - /** Catalogue capabilities. Catalogue entries drive the visibility of the Kibana homepage options. */ - catalogue: Record; - - /** Custom capabilities, registered by plugins. */ - [key: string]: Record>; -} +export { Capabilities }; /** * See {@link CapabilitiesSetup} diff --git a/src/core/types/capabilities.ts b/src/core/types/capabilities.ts new file mode 100644 index 0000000000000..c2ca09528f085 --- /dev/null +++ b/src/core/types/capabilities.ts @@ -0,0 +1,41 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * The read-only set of capabilities available for the current UI session. + * Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, + * and the boolean is a flag indicating if the capability is enabled or disabled. + * + * @public + */ +export interface Capabilities { + /** Navigation link capabilities. */ + navLinks: Record; + + /** Management section capabilities. */ + management: { + [sectionId: string]: Record; + }; + + /** Catalogue capabilities. Catalogue entries drive the visibility of the Kibana homepage options. */ + catalogue: Record; + + /** Custom capabilities, registered by plugins. */ + [key: string]: Record>; +} diff --git a/src/core/types/index.ts b/src/core/types/index.ts index 3aae17e6ea594..d01b514c770a7 100644 --- a/src/core/types/index.ts +++ b/src/core/types/index.ts @@ -22,3 +22,4 @@ * types are stripped. */ export * from './core_service'; +export * from './capabilities'; From bd7064ee38551303abef83a68a6b266aecee006b Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Tue, 26 Nov 2019 10:04:11 +0100 Subject: [PATCH 19/34] update generated docs --- .../kibana-plugin-server.capabilitiessetup.md | 6 +++-- ...itiessetup.registercapabilitiesprovider.md | 22 +++++++++++++++++++ ...itiessetup.registercapabilitiesswitcher.md | 22 +++++++++++++++++++ .../kibana-plugin-server.capabilitiesstart.md | 4 +++- ...r.capabilitiesstart.resolvecapabilities.md | 2 ++ .../core/server/kibana-plugin-server.md | 4 ++-- src/core/server/server.api.md | 15 ++++--------- 7 files changed, 59 insertions(+), 16 deletions(-) diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.md b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.md index 8bfb7a7d98d68..1f340fbabbc61 100644 --- a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.md +++ b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.md @@ -4,6 +4,8 @@ ## CapabilitiesSetup interface +APIs to manage the [Capabilities](./kibana-plugin-server.capabilities.md) that will be used by the application. + Signature: ```typescript @@ -14,6 +16,6 @@ export interface CapabilitiesSetup | Method | Description | | --- | --- | -| [registerCapabilitiesProvider(provider)](./kibana-plugin-server.capabilitiessetup.registercapabilitiesprovider.md) | | -| [registerCapabilitiesSwitcher(switcher)](./kibana-plugin-server.capabilitiessetup.registercapabilitiesswitcher.md) | | +| [registerCapabilitiesProvider(provider)](./kibana-plugin-server.capabilitiessetup.registercapabilitiesprovider.md) | Register a to be used when resolving capabilities. | +| [registerCapabilitiesSwitcher(switcher)](./kibana-plugin-server.capabilitiessetup.registercapabilitiesswitcher.md) | Register a to be used when resolving capabilities. | diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesprovider.md b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesprovider.md index 5af88914ec8e9..862cbfae5fbfc 100644 --- a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesprovider.md +++ b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesprovider.md @@ -4,6 +4,8 @@ ## CapabilitiesSetup.registerCapabilitiesProvider() method +Register a to be used when resolving capabilities. + Signature: ```typescript @@ -20,3 +22,23 @@ registerCapabilitiesProvider(provider: CapabilitiesProvider): void; `void` +## Example + + +```ts +// my-plugin/server/plugin.ts +public setup(core: CoreSetup, deps: {}) { + core.capabilities.registerCapabilitiesProvider(() => { + return { + catalogue: { + myPlugin: true, + }, + myPlugin: { + feature: true, + }, + } + }) +} + +``` + diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesswitcher.md b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesswitcher.md index cfa0c31b1fa78..47349fb8e287b 100644 --- a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesswitcher.md +++ b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesswitcher.md @@ -4,6 +4,8 @@ ## CapabilitiesSetup.registerCapabilitiesSwitcher() method +Register a to be used when resolving capabilities. + Signature: ```typescript @@ -20,3 +22,23 @@ registerCapabilitiesSwitcher(switcher: CapabilitiesSwitcher): void; `void` +## Remarks + +A capabilities switcher can only change the state of existing capabilities. Adding or removing capabilities when invoking the switcher will raise an error. + +## Example + + +```ts +// my-plugin/server/plugin.ts +public setup(core: CoreSetup, deps: {}) { + core.capabilities.registerCapabilitiesSwitcher((request, capabilities) => { + if(myPluginApi.shouldRestrictBecauseOf(request)) { + return myPluginApi.restrictCapabilities(capabilities); + } + return capabilities; + }) +} + +``` + diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiesstart.md b/docs/development/core/server/kibana-plugin-server.capabilitiesstart.md index a8ae9b9e66116..55cc1aed76b5b 100644 --- a/docs/development/core/server/kibana-plugin-server.capabilitiesstart.md +++ b/docs/development/core/server/kibana-plugin-server.capabilitiesstart.md @@ -4,6 +4,8 @@ ## CapabilitiesStart interface +APIs to access the application [Capabilities](./kibana-plugin-server.capabilities.md). + Signature: ```typescript @@ -14,5 +16,5 @@ export interface CapabilitiesStart | Method | Description | | --- | --- | -| [resolveCapabilities(request)](./kibana-plugin-server.capabilitiesstart.resolvecapabilities.md) | | +| [resolveCapabilities(request)](./kibana-plugin-server.capabilitiesstart.resolvecapabilities.md) | Resolve the [Capabilities](./kibana-plugin-server.capabilities.md) to be used for given request | diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiesstart.resolvecapabilities.md b/docs/development/core/server/kibana-plugin-server.capabilitiesstart.resolvecapabilities.md index cc902eea08e5c..95b751dd4fc95 100644 --- a/docs/development/core/server/kibana-plugin-server.capabilitiesstart.resolvecapabilities.md +++ b/docs/development/core/server/kibana-plugin-server.capabilitiesstart.resolvecapabilities.md @@ -4,6 +4,8 @@ ## CapabilitiesStart.resolveCapabilities() method +Resolve the [Capabilities](./kibana-plugin-server.capabilities.md) to be used for given request + Signature: ```typescript diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 6ea87b58c2844..2a399e7f79558 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -43,8 +43,8 @@ The plugin integrates with the core system via lifecycle events: `setup` | [AuthToolkit](./kibana-plugin-server.authtoolkit.md) | A tool set defining an outcome of Auth interceptor for incoming request. | | [CallAPIOptions](./kibana-plugin-server.callapioptions.md) | The set of options that defines how API call should be made and result be processed. | | [Capabilities](./kibana-plugin-server.capabilities.md) | The read-only set of capabilities available for the current UI session. Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, and the boolean is a flag indicating if the capability is enabled or disabled. | -| [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | | -| [CapabilitiesStart](./kibana-plugin-server.capabilitiesstart.md) | | +| [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | APIs to manage the [Capabilities](./kibana-plugin-server.capabilities.md) that will be used by the application. | +| [CapabilitiesStart](./kibana-plugin-server.capabilitiesstart.md) | APIs to access the application [Capabilities](./kibana-plugin-server.capabilities.md). | | [ContextSetup](./kibana-plugin-server.contextsetup.md) | An object that handles registration of context providers and configuring handlers with context. | | [CoreSetup](./kibana-plugin-server.coresetup.md) | Context passed to the plugins setup method. | | [CoreStart](./kibana-plugin-server.corestart.md) | Context passed to the plugins start method. | diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 7cba4f0e8e448..882938b8f1509 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -477,25 +477,18 @@ export interface Capabilities { navLinks: Record; } -// Warning: (ae-missing-release-tag) "CapabilitiesSetup" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export interface CapabilitiesSetup { // Warning: (ae-forgotten-export) The symbol "CapabilitiesProvider" needs to be exported by the entry point index.d.ts - // - // (undocumented) + // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "CapabilitiesProvider" registerCapabilitiesProvider(provider: CapabilitiesProvider): void; // Warning: (ae-forgotten-export) The symbol "CapabilitiesSwitcher" needs to be exported by the entry point index.d.ts - // - // (undocumented) + // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "CapabilitiesSwitcher" registerCapabilitiesSwitcher(switcher: CapabilitiesSwitcher): void; } -// Warning: (ae-missing-release-tag) "CapabilitiesStart" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export interface CapabilitiesStart { - // (undocumented) resolveCapabilities(request: KibanaRequest): Promise; } From bd98c784ff5eeeedd0802ca471c32d4122bd205b Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Tue, 26 Nov 2019 10:21:42 +0100 Subject: [PATCH 20/34] add service tests --- .../capabilities/capabilities_service.test.ts | 51 ++++++++++++++++++- .../capabilities/resolve_capabilities.ts | 2 +- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/core/server/capabilities/capabilities_service.test.ts b/src/core/server/capabilities/capabilities_service.test.ts index dcd98090f2d27..6b40694ac8210 100644 --- a/src/core/server/capabilities/capabilities_service.test.ts +++ b/src/core/server/capabilities/capabilities_service.test.ts @@ -19,12 +19,13 @@ import { httpServiceMock, HttpServiceSetupMock } from '../http/http_service.mock'; import { mockRouter, RouterMock } from '../http/router/router.mock'; -import { CapabilitiesService } from './capabilities_service'; +import { CapabilitiesService, CapabilitiesSetup } from './capabilities_service'; import { mockCoreContext } from '../core_context.mock'; describe('CapabilitiesService', () => { let http: HttpServiceSetupMock; let service: CapabilitiesService; + let setup: CapabilitiesSetup; let router: RouterMock; beforeEach(() => { @@ -36,12 +37,58 @@ describe('CapabilitiesService', () => { describe('#setup()', () => { beforeEach(() => { - service.setup({ http }); + setup = service.setup({ http }); }); it('registers the capabilities route', async () => { expect(http.createRouter).toHaveBeenCalledWith('/api/core/capabilities'); expect(router.post).toHaveBeenCalledWith(expect.any(Object), expect.any(Function)); }); + + it('allows to register capabilities providers', async () => { + setup.registerCapabilitiesProvider(() => ({ + navLinks: { myLink: true }, + catalogue: { myPlugin: true }, + })); + const start = service.start(); + expect(await start.resolveCapabilities({} as any)).toMatchInlineSnapshot(` + Object { + "catalogue": Object { + "myPlugin": true, + }, + "management": Object {}, + "navLinks": Object { + "myLink": true, + }, + } + `); + }); + + it('allows to register capabilities switchers', async () => { + setup.registerCapabilitiesProvider(() => ({ + catalogue: { a: true, b: true, c: true }, + })); + setup.registerCapabilitiesSwitcher((req, capabilities) => { + return { + ...capabilities, + catalogue: { + ...capabilities.catalogue, + b: false, + }, + }; + }); + const start = service.start(); + expect(await start.resolveCapabilities({} as any)).toMatchInlineSnapshot(` + Object { + "catalogue": Object { + "a": true, + "b": false, + "c": true, + }, + "management": Object {}, + "navLinks": Object {}, + } + `); + }); }); }); diff --git a/src/core/server/capabilities/resolve_capabilities.ts b/src/core/server/capabilities/resolve_capabilities.ts index 15a674d4b2f82..178a468d594ed 100644 --- a/src/core/server/capabilities/resolve_capabilities.ts +++ b/src/core/server/capabilities/resolve_capabilities.ts @@ -48,7 +48,7 @@ export const resolveCapabilities = async ( ...acc, [app]: true, }), - {} + capabilities.navLinks ), }; return switchers.reduce(async (caps, switcher) => { From d4ce55c28876c36f32a65c75cea5777046346667 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Tue, 26 Nov 2019 11:25:20 +0100 Subject: [PATCH 21/34] protecting resolveCapabilities against added/removed capabilities --- .../capabilities/capabilities_service.ts | 4 +- .../capabilities/resolve_capabilities.test.ts | 98 ++++++++++++++++++- .../capabilities/resolve_capabilities.ts | 34 ++++++- src/core/server/capabilities/types.ts | 2 +- 4 files changed, 130 insertions(+), 8 deletions(-) diff --git a/src/core/server/capabilities/capabilities_service.ts b/src/core/server/capabilities/capabilities_service.ts index fb28255234770..474fa97c3a820 100644 --- a/src/core/server/capabilities/capabilities_service.ts +++ b/src/core/server/capabilities/capabilities_service.ts @@ -70,8 +70,8 @@ export interface CapabilitiesSetup { * ``` * * @remarks - * A capabilities switcher can only change the state of existing capabilities. Adding or removing - * capabilities when invoking the switcher will raise an error. + * A capabilities switcher can only change the state of existing capabilities. + * capabilities added or removed when invoking the switcher will be ignored. */ registerCapabilitiesSwitcher(switcher: CapabilitiesSwitcher): void; } diff --git a/src/core/server/capabilities/resolve_capabilities.test.ts b/src/core/server/capabilities/resolve_capabilities.test.ts index 2a2e603a8582c..372efeff21ae2 100644 --- a/src/core/server/capabilities/resolve_capabilities.test.ts +++ b/src/core/server/capabilities/resolve_capabilities.test.ts @@ -35,12 +35,12 @@ describe('resolveCapabilities', () => { request = httpServerMock.createKibanaRequest(); }); - it('should returns the initial capabilities if no switcher are used', async () => { + it('returns the initial capabilities if no switcher are used', async () => { const result = await resolveCapabilities(defaultCaps, [], request, []); expect(result).toEqual(defaultCaps); }); - it('should apply the switcher to the capabilities ', async () => { + it('applies the switcher to the capabilities ', async () => { const caps = { ...defaultCaps, catalogue: { @@ -67,4 +67,98 @@ describe('resolveCapabilities', () => { } `); }); + + it('does not mutate the input capabilities', async () => { + const caps = { + ...defaultCaps, + catalogue: { + A: true, + B: true, + }, + }; + const switcher = (req: KibanaRequest, capabilities: Capabilities) => ({ + ...capabilities, + catalogue: { + ...capabilities.catalogue, + A: false, + }, + }); + await resolveCapabilities(caps, [switcher], request, []); + expect(caps.catalogue).toEqual({ + A: true, + B: true, + }); + }); + + it('ignores any added capability from the switcher', async () => { + const caps = { + ...defaultCaps, + catalogue: { + A: true, + B: true, + }, + }; + const switcher = (req: KibanaRequest, capabilities: Capabilities) => ({ + ...capabilities, + catalogue: { + ...capabilities.catalogue, + C: false, + }, + }); + const result = await resolveCapabilities(caps, [switcher], request, []); + expect(result.catalogue).toEqual({ + A: true, + B: true, + }); + }); + + it('ignores any removed capability from the switcher', async () => { + const caps = { + ...defaultCaps, + catalogue: { + A: true, + B: true, + C: true, + }, + }; + const switcher = (req: KibanaRequest, capabilities: Capabilities) => ({ + ...capabilities, + catalogue: Object.entries(capabilities.catalogue) + .filter(([key]) => key !== 'B') + .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}), + }); + const result = await resolveCapabilities(caps, [switcher], request, []); + expect(result.catalogue).toEqual({ + A: true, + B: true, + C: true, + }); + }); + + it('ignores any capability type mutation from the switcher', async () => { + const caps = { + ...defaultCaps, + section: { + boolean: true, + record: { + entry: true, + }, + }, + }; + const switcher = (req: KibanaRequest, capabilities: Capabilities) => ({ + section: { + boolean: { + entry: false, + }, + record: false, + }, + }); + const result = await resolveCapabilities(caps, [switcher], request, []); + expect(result.section).toEqual({ + boolean: true, + record: { + entry: true, + }, + }); + }); }); diff --git a/src/core/server/capabilities/resolve_capabilities.ts b/src/core/server/capabilities/resolve_capabilities.ts index 178a468d594ed..dcb93bdca5f16 100644 --- a/src/core/server/capabilities/resolve_capabilities.ts +++ b/src/core/server/capabilities/resolve_capabilities.ts @@ -17,6 +17,7 @@ * under the License. */ +import { cloneDeep } from 'lodash'; import { Capabilities, CapabilitiesSwitcher } from './types'; import { KibanaRequest } from '../http'; @@ -41,7 +42,7 @@ export const resolveCapabilities = async ( request: KibanaRequest, applications: string[] ): Promise => { - const mergedCaps = { + const mergedCaps = cloneDeep({ ...capabilities, navLinks: applications.reduce( (acc, app) => ({ @@ -50,8 +51,35 @@ export const resolveCapabilities = async ( }), capabilities.navLinks ), - }; + }); return switchers.reduce(async (caps, switcher) => { - return switcher(request, await caps); + const resolvedCaps = await caps; + const changes = await switcher(request, resolvedCaps); + return recursiveApplyChanges(resolvedCaps, changes); }, Promise.resolve(mergedCaps)); }; + +function recursiveApplyChanges< + TDestination extends Record, + TSource extends Record +>(destination: TDestination, source: TSource): TDestination { + return Object.keys(destination) + .map(key => { + const orig = destination[key]; + const changed = source[key]; + if (changed == null) { + return [key, orig]; + } + if (typeof orig === 'object' && typeof changed === 'object') { + return [key, recursiveApplyChanges(orig, changed)]; + } + return [key, typeof orig === typeof changed ? changed : orig]; + }) + .reduce( + (acc, [key, value]) => ({ + ...acc, + [key]: value, + }), + {} as TDestination + ); +} diff --git a/src/core/server/capabilities/types.ts b/src/core/server/capabilities/types.ts index ca846185567a2..105233761a437 100644 --- a/src/core/server/capabilities/types.ts +++ b/src/core/server/capabilities/types.ts @@ -35,4 +35,4 @@ export type CapabilitiesProvider = () => Partial; export type CapabilitiesSwitcher = ( request: KibanaRequest, uiCapabilities: Capabilities -) => Capabilities | Promise; +) => Partial | Promise>; From 7746d5d45e39c4467af1427c91a5a793575953d1 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Tue, 26 Nov 2019 11:28:00 +0100 Subject: [PATCH 22/34] update generated docs --- ...gin-server.capabilitiessetup.registercapabilitiesswitcher.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesswitcher.md b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesswitcher.md index 47349fb8e287b..08a1a511d3ea4 100644 --- a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesswitcher.md +++ b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesswitcher.md @@ -24,7 +24,7 @@ registerCapabilitiesSwitcher(switcher: CapabilitiesSwitcher): void; ## Remarks -A capabilities switcher can only change the state of existing capabilities. Adding or removing capabilities when invoking the switcher will raise an error. +A capabilities switcher can only change the state of existing capabilities. capabilities added or removed when invoking the switcher will be ignored. ## Example From e99dba862a995011acea36659b12d373f88c8c66 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Tue, 26 Nov 2019 11:46:36 +0100 Subject: [PATCH 23/34] adapt mocks due to rebase --- src/core/server/http/http_service.mock.ts | 15 ++------------- src/core/server/http/router/router.mock.ts | 1 + 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index 0d3f3d1b9696b..c7f6cdb2bb422 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -24,7 +24,6 @@ import { HttpService } from './http_service'; import { OnPreAuthToolkit } from './lifecycle/on_pre_auth'; import { AuthToolkit } from './lifecycle/auth'; import { sessionStorageMock } from './cookie_session_storage.mocks'; -import { IRouter } from './router'; import { OnPostAuthToolkit } from './lifecycle/on_post_auth'; export type HttpServiceSetupMock = jest.Mocked & { @@ -39,16 +38,6 @@ const createBasePathMock = (): jest.Mocked remove: jest.fn(), }); -const createRouterMock = (): jest.Mocked => ({ - routerPath: '/', - get: jest.fn(), - post: jest.fn(), - put: jest.fn(), - delete: jest.fn(), - getRoutes: jest.fn(), - handleLegacyErrors: jest.fn().mockImplementation(handler => handler), -}); - const createSetupContractMock = () => { const setupContract: HttpServiceSetupMock = { // we can mock other hapi server methods when we need it @@ -75,7 +64,7 @@ const createSetupContractMock = () => { setupContract.createCookieSessionStorageFactory.mockResolvedValue( sessionStorageMock.createFactory() ); - setupContract.createRouter.mockImplementation(createRouterMock); + setupContract.createRouter.mockImplementation(() => mockRouter.create()); return setupContract; }; @@ -110,5 +99,5 @@ export const httpServiceMock = { createOnPreAuthToolkit: createOnPreAuthToolkitMock, createOnPostAuthToolkit: createOnPostAuthToolkitMock, createAuthToolkit: createAuthToolkitMock, - createRouter: createRouterMock, + createRouter: mockRouter.create, }; diff --git a/src/core/server/http/router/router.mock.ts b/src/core/server/http/router/router.mock.ts index 3081ccf8470c7..9446dee0e5186 100644 --- a/src/core/server/http/router/router.mock.ts +++ b/src/core/server/http/router/router.mock.ts @@ -29,6 +29,7 @@ function create({ routerPath = '' }: { routerPath?: string } = {}): RouterMock { delete: jest.fn(), put: jest.fn(), getRoutes: jest.fn(), + handleLegacyErrors: jest.fn().mockImplementation(handler => handler), }; } From f6c76d2582cd9e1751568ae65427d73892ed935d Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Tue, 26 Nov 2019 13:15:36 +0100 Subject: [PATCH 24/34] add forgotten exports --- .../kibana-plugin-server.capabilitiesprovider.md | 13 +++++++++++++ .../kibana-plugin-server.capabilitiessetup.md | 4 ++-- ...apabilitiessetup.registercapabilitiesprovider.md | 2 +- ...apabilitiessetup.registercapabilitiesswitcher.md | 2 +- .../kibana-plugin-server.capabilitiesswitcher.md | 13 +++++++++++++ .../development/core/server/kibana-plugin-server.md | 2 ++ src/core/server/index.ts | 2 +- src/core/server/server.api.md | 10 ++++++---- 8 files changed, 39 insertions(+), 9 deletions(-) create mode 100644 docs/development/core/server/kibana-plugin-server.capabilitiesprovider.md create mode 100644 docs/development/core/server/kibana-plugin-server.capabilitiesswitcher.md diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiesprovider.md b/docs/development/core/server/kibana-plugin-server.capabilitiesprovider.md new file mode 100644 index 0000000000000..66e5d256ada66 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.capabilitiesprovider.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CapabilitiesProvider](./kibana-plugin-server.capabilitiesprovider.md) + +## CapabilitiesProvider type + +See [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) + +Signature: + +```typescript +export declare type CapabilitiesProvider = () => Partial; +``` diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.md b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.md index 1f340fbabbc61..c94d74edaaea0 100644 --- a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.md +++ b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.md @@ -16,6 +16,6 @@ export interface CapabilitiesSetup | Method | Description | | --- | --- | -| [registerCapabilitiesProvider(provider)](./kibana-plugin-server.capabilitiessetup.registercapabilitiesprovider.md) | Register a to be used when resolving capabilities. | -| [registerCapabilitiesSwitcher(switcher)](./kibana-plugin-server.capabilitiessetup.registercapabilitiesswitcher.md) | Register a to be used when resolving capabilities. | +| [registerCapabilitiesProvider(provider)](./kibana-plugin-server.capabilitiessetup.registercapabilitiesprovider.md) | Register a [CapabilitiesProvider](./kibana-plugin-server.capabilitiesprovider.md) to be used when resolving capabilities. | +| [registerCapabilitiesSwitcher(switcher)](./kibana-plugin-server.capabilitiessetup.registercapabilitiesswitcher.md) | Register a [CapabilitiesSwitcher](./kibana-plugin-server.capabilitiesswitcher.md) to be used when resolving capabilities. | diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesprovider.md b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesprovider.md index 862cbfae5fbfc..1d5e189a69c4a 100644 --- a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesprovider.md +++ b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesprovider.md @@ -4,7 +4,7 @@ ## CapabilitiesSetup.registerCapabilitiesProvider() method -Register a to be used when resolving capabilities. +Register a [CapabilitiesProvider](./kibana-plugin-server.capabilitiesprovider.md) to be used when resolving capabilities. Signature: diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesswitcher.md b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesswitcher.md index 08a1a511d3ea4..2de3fa9d8e10b 100644 --- a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesswitcher.md +++ b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesswitcher.md @@ -4,7 +4,7 @@ ## CapabilitiesSetup.registerCapabilitiesSwitcher() method -Register a to be used when resolving capabilities. +Register a [CapabilitiesSwitcher](./kibana-plugin-server.capabilitiesswitcher.md) to be used when resolving capabilities. Signature: diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiesswitcher.md b/docs/development/core/server/kibana-plugin-server.capabilitiesswitcher.md new file mode 100644 index 0000000000000..dd6af54376896 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.capabilitiesswitcher.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CapabilitiesSwitcher](./kibana-plugin-server.capabilitiesswitcher.md) + +## CapabilitiesSwitcher type + +See [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) + +Signature: + +```typescript +export declare type CapabilitiesSwitcher = (request: KibanaRequest, uiCapabilities: Capabilities) => Partial | Promise>; +``` diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 2a399e7f79558..451469145e1cb 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -138,6 +138,8 @@ The plugin integrates with the core system via lifecycle events: `setup` | [AuthenticationHandler](./kibana-plugin-server.authenticationhandler.md) | See [AuthToolkit](./kibana-plugin-server.authtoolkit.md). | | [AuthHeaders](./kibana-plugin-server.authheaders.md) | Auth Headers map | | [AuthResult](./kibana-plugin-server.authresult.md) | | +| [CapabilitiesProvider](./kibana-plugin-server.capabilitiesprovider.md) | See [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | +| [CapabilitiesSwitcher](./kibana-plugin-server.capabilitiesswitcher.md) | See [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | | [ConfigPath](./kibana-plugin-server.configpath.md) | | | [ElasticsearchClientConfig](./kibana-plugin-server.elasticsearchclientconfig.md) | | | [GetAuthHeaders](./kibana-plugin-server.getauthheaders.md) | Get headers to authenticate a user against Elasticsearch. | diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 09fb6a76f7a58..b14b1aca64b7a 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -48,7 +48,7 @@ import { SavedObjectsClientContract } from './saved_objects/types'; import { CapabilitiesSetup, CapabilitiesStart } from './capabilities'; export { bootstrap } from './bootstrap'; -export { Capabilities } from './capabilities'; +export { Capabilities, CapabilitiesProvider, CapabilitiesSwitcher } from './capabilities'; export { ConfigPath, ConfigService, EnvironmentMode, PackageInfo } from './config'; export { IContextContainer, diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 882938b8f1509..da862b090acaa 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -477,13 +477,12 @@ export interface Capabilities { navLinks: Record; } +// @public +export type CapabilitiesProvider = () => Partial; + // @public export interface CapabilitiesSetup { - // Warning: (ae-forgotten-export) The symbol "CapabilitiesProvider" needs to be exported by the entry point index.d.ts - // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "CapabilitiesProvider" registerCapabilitiesProvider(provider: CapabilitiesProvider): void; - // Warning: (ae-forgotten-export) The symbol "CapabilitiesSwitcher" needs to be exported by the entry point index.d.ts - // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "CapabilitiesSwitcher" registerCapabilitiesSwitcher(switcher: CapabilitiesSwitcher): void; } @@ -492,6 +491,9 @@ export interface CapabilitiesStart { resolveCapabilities(request: KibanaRequest): Promise; } +// @public +export type CapabilitiesSwitcher = (request: KibanaRequest, uiCapabilities: Capabilities) => Partial | Promise>; + // @public export class ClusterClient implements IClusterClient { constructor(config: ElasticsearchClientConfig, log: Logger, getAuthHeaders?: GetAuthHeaders); From 707a2c472bc7ce22904fcb50935cc86154dd11bf Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Wed, 27 Nov 2019 08:06:17 +0100 Subject: [PATCH 25/34] improve capabilities routes registering --- .../capabilities/capabilities_service.test.ts | 3 +- .../capabilities/capabilities_service.ts | 60 +++++++------------ 2 files changed, 23 insertions(+), 40 deletions(-) diff --git a/src/core/server/capabilities/capabilities_service.test.ts b/src/core/server/capabilities/capabilities_service.test.ts index 6b40694ac8210..1307b6e7494d7 100644 --- a/src/core/server/capabilities/capabilities_service.test.ts +++ b/src/core/server/capabilities/capabilities_service.test.ts @@ -40,8 +40,9 @@ describe('CapabilitiesService', () => { setup = service.setup({ http }); }); - it('registers the capabilities route', async () => { + it('registers the capabilities routes', async () => { expect(http.createRouter).toHaveBeenCalledWith('/api/core/capabilities'); + expect(router.post).toHaveBeenCalledTimes(2); expect(router.post).toHaveBeenCalledWith(expect.any(Object), expect.any(Function)); }); diff --git a/src/core/server/capabilities/capabilities_service.ts b/src/core/server/capabilities/capabilities_service.ts index 474fa97c3a820..4c27209ffa5f7 100644 --- a/src/core/server/capabilities/capabilities_service.ts +++ b/src/core/server/capabilities/capabilities_service.ts @@ -139,45 +139,27 @@ export class CapabilitiesService { private setupCapabilitiesRoute(http: InternalHttpServiceSetup) { const router = http.createRouter('/api/core/capabilities'); - router.post( - { - path: '', - options: { - authRequired: true, + [true, false].forEach(authRequired => { + router.post( + { + path: authRequired ? '' : '/defaults', + options: { + authRequired, + }, + validate: { + body: schema.object({ + applications: schema.arrayOf(schema.string()), + }), + }, }, - validate: { - body: schema.object({ - applications: schema.arrayOf(schema.string()), - }), - }, - }, - async (ctx, req, res) => { - const { applications } = req.body; - const capabilities = await this.resolveCapabilities(req, applications); - return res.ok({ - body: capabilities, - }); - } - ); - router.post( - { - path: '/defaults', - options: { - authRequired: false, - }, - validate: { - body: schema.object({ - applications: schema.arrayOf(schema.string()), - }), - }, - }, - async (ctx, req, res) => { - const { applications } = req.body; - const capabilities = await this.resolveCapabilities(req, applications); - return res.ok({ - body: capabilities, - }); - } - ); + async (ctx, req, res) => { + const { applications } = req.body; + const capabilities = await this.resolveCapabilities(req, applications); + return res.ok({ + body: capabilities, + }); + } + ); + }); } } From a1cc7fce65f66b3e3d324af78300a6e5b70a70d7 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Wed, 27 Nov 2019 08:15:51 +0100 Subject: [PATCH 26/34] name capabilities registering methods --- .../kibana-plugin-server.capabilitiessetup.md | 4 ++-- ...n-server.capabilitiessetup.registerprovider.md} | 8 ++++---- ...n-server.capabilitiessetup.registerswitcher.md} | 10 +++++----- .../capabilities/capabilities_service.mock.ts | 4 ++-- .../capabilities/capabilities_service.test.ts | 6 +++--- .../server/capabilities/capabilities_service.ts | 14 +++++++------- .../integration_tests/capabilities_service.test.ts | 2 +- src/core/server/plugins/plugin_context.ts | 4 ++-- src/core/server/server.api.md | 4 ++-- .../server/capabilities/capabilities_mixin.test.ts | 2 +- .../server/capabilities/capabilities_mixin.ts | 4 +--- x-pack/plugins/security/server/plugin.ts | 2 +- x-pack/plugins/spaces/server/plugin.ts | 2 +- 13 files changed, 32 insertions(+), 34 deletions(-) rename docs/development/core/server/{kibana-plugin-server.capabilitiessetup.registercapabilitiesprovider.md => kibana-plugin-server.capabilitiessetup.registerprovider.md} (68%) rename docs/development/core/server/{kibana-plugin-server.capabilitiessetup.registercapabilitiesswitcher.md => kibana-plugin-server.capabilitiessetup.registerswitcher.md} (66%) diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.md b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.md index c94d74edaaea0..64cc7ed7da2fb 100644 --- a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.md +++ b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.md @@ -16,6 +16,6 @@ export interface CapabilitiesSetup | Method | Description | | --- | --- | -| [registerCapabilitiesProvider(provider)](./kibana-plugin-server.capabilitiessetup.registercapabilitiesprovider.md) | Register a [CapabilitiesProvider](./kibana-plugin-server.capabilitiesprovider.md) to be used when resolving capabilities. | -| [registerCapabilitiesSwitcher(switcher)](./kibana-plugin-server.capabilitiessetup.registercapabilitiesswitcher.md) | Register a [CapabilitiesSwitcher](./kibana-plugin-server.capabilitiesswitcher.md) to be used when resolving capabilities. | +| [registerProvider(provider)](./kibana-plugin-server.capabilitiessetup.registerprovider.md) | Register a [CapabilitiesProvider](./kibana-plugin-server.capabilitiesprovider.md) to be used when resolving capabilities. | +| [registerSwitcher(switcher)](./kibana-plugin-server.capabilitiessetup.registerswitcher.md) | Register a [CapabilitiesSwitcher](./kibana-plugin-server.capabilitiesswitcher.md) to be used when resolving capabilities. | diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesprovider.md b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registerprovider.md similarity index 68% rename from docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesprovider.md rename to docs/development/core/server/kibana-plugin-server.capabilitiessetup.registerprovider.md index 1d5e189a69c4a..0148ae377fd71 100644 --- a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesprovider.md +++ b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registerprovider.md @@ -1,15 +1,15 @@ -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) > [registerCapabilitiesProvider](./kibana-plugin-server.capabilitiessetup.registercapabilitiesprovider.md) +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) > [registerProvider](./kibana-plugin-server.capabilitiessetup.registerprovider.md) -## CapabilitiesSetup.registerCapabilitiesProvider() method +## CapabilitiesSetup.registerProvider() method Register a [CapabilitiesProvider](./kibana-plugin-server.capabilitiesprovider.md) to be used when resolving capabilities. Signature: ```typescript -registerCapabilitiesProvider(provider: CapabilitiesProvider): void; +registerProvider(provider: CapabilitiesProvider): void; ``` ## Parameters @@ -28,7 +28,7 @@ registerCapabilitiesProvider(provider: CapabilitiesProvider): void; ```ts // my-plugin/server/plugin.ts public setup(core: CoreSetup, deps: {}) { - core.capabilities.registerCapabilitiesProvider(() => { + core.capabilities.registerProvider(() => { return { catalogue: { myPlugin: true, diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesswitcher.md b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registerswitcher.md similarity index 66% rename from docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesswitcher.md rename to docs/development/core/server/kibana-plugin-server.capabilitiessetup.registerswitcher.md index 2de3fa9d8e10b..aeefa0e2bface 100644 --- a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registercapabilitiesswitcher.md +++ b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registerswitcher.md @@ -1,15 +1,15 @@ -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) > [registerCapabilitiesSwitcher](./kibana-plugin-server.capabilitiessetup.registercapabilitiesswitcher.md) +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) > [registerSwitcher](./kibana-plugin-server.capabilitiessetup.registerswitcher.md) -## CapabilitiesSetup.registerCapabilitiesSwitcher() method +## CapabilitiesSetup.registerSwitcher() method Register a [CapabilitiesSwitcher](./kibana-plugin-server.capabilitiesswitcher.md) to be used when resolving capabilities. Signature: ```typescript -registerCapabilitiesSwitcher(switcher: CapabilitiesSwitcher): void; +registerSwitcher(switcher: CapabilitiesSwitcher): void; ``` ## Parameters @@ -32,9 +32,9 @@ A capabilities switcher can only change the state of existing capabilities. capa ```ts // my-plugin/server/plugin.ts public setup(core: CoreSetup, deps: {}) { - core.capabilities.registerCapabilitiesSwitcher((request, capabilities) => { + core.capabilities.registerSwitcher((request, capabilities) => { if(myPluginApi.shouldRestrictBecauseOf(request)) { - return myPluginApi.restrictCapabilities(capabilities); + return myPluginApi.disableSomeCapabilities(capabilities); } return capabilities; }) diff --git a/src/core/server/capabilities/capabilities_service.mock.ts b/src/core/server/capabilities/capabilities_service.mock.ts index 2ce9deb83565d..3f31eca8339d8 100644 --- a/src/core/server/capabilities/capabilities_service.mock.ts +++ b/src/core/server/capabilities/capabilities_service.mock.ts @@ -21,8 +21,8 @@ import { CapabilitiesService, CapabilitiesSetup, CapabilitiesStart } from './cap const createSetupContractMock = () => { const setupContract: jest.Mocked = { - registerCapabilitiesProvider: jest.fn(), - registerCapabilitiesSwitcher: jest.fn(), + registerProvider: jest.fn(), + registerSwitcher: jest.fn(), }; return setupContract; }; diff --git a/src/core/server/capabilities/capabilities_service.test.ts b/src/core/server/capabilities/capabilities_service.test.ts index 1307b6e7494d7..d47b914e36d6a 100644 --- a/src/core/server/capabilities/capabilities_service.test.ts +++ b/src/core/server/capabilities/capabilities_service.test.ts @@ -47,7 +47,7 @@ describe('CapabilitiesService', () => { }); it('allows to register capabilities providers', async () => { - setup.registerCapabilitiesProvider(() => ({ + setup.registerProvider(() => ({ navLinks: { myLink: true }, catalogue: { myPlugin: true }, })); @@ -66,10 +66,10 @@ describe('CapabilitiesService', () => { }); it('allows to register capabilities switchers', async () => { - setup.registerCapabilitiesProvider(() => ({ + setup.registerProvider(() => ({ catalogue: { a: true, b: true, c: true }, })); - setup.registerCapabilitiesSwitcher((req, capabilities) => { + setup.registerSwitcher((req, capabilities) => { return { ...capabilities, catalogue: { diff --git a/src/core/server/capabilities/capabilities_service.ts b/src/core/server/capabilities/capabilities_service.ts index 4c27209ffa5f7..985bb1175800f 100644 --- a/src/core/server/capabilities/capabilities_service.ts +++ b/src/core/server/capabilities/capabilities_service.ts @@ -38,7 +38,7 @@ export interface CapabilitiesSetup { * ```ts * // my-plugin/server/plugin.ts * public setup(core: CoreSetup, deps: {}) { - * core.capabilities.registerCapabilitiesProvider(() => { + * core.capabilities.registerProvider(() => { * return { * catalogue: { * myPlugin: true, @@ -51,7 +51,7 @@ export interface CapabilitiesSetup { * } * ``` */ - registerCapabilitiesProvider(provider: CapabilitiesProvider): void; + registerProvider(provider: CapabilitiesProvider): void; /** * Register a {@link CapabilitiesSwitcher} to be used when resolving capabilities. @@ -60,9 +60,9 @@ export interface CapabilitiesSetup { * ```ts * // my-plugin/server/plugin.ts * public setup(core: CoreSetup, deps: {}) { - * core.capabilities.registerCapabilitiesSwitcher((request, capabilities) => { + * core.capabilities.registerSwitcher((request, capabilities) => { * if(myPluginApi.shouldRestrictBecauseOf(request)) { - * return myPluginApi.restrictCapabilities(capabilities); + * return myPluginApi.disableSomeCapabilities(capabilities); * } * return capabilities; * }) @@ -73,7 +73,7 @@ export interface CapabilitiesSetup { * A capabilities switcher can only change the state of existing capabilities. * capabilities added or removed when invoking the switcher will be ignored. */ - registerCapabilitiesSwitcher(switcher: CapabilitiesSwitcher): void; + registerSwitcher(switcher: CapabilitiesSwitcher): void; } /** @@ -122,10 +122,10 @@ export class CapabilitiesService { this.setupCapabilitiesRoute(setupDeps.http); return { - registerCapabilitiesProvider: (provider: CapabilitiesProvider) => { + registerProvider: (provider: CapabilitiesProvider) => { this.capabilitiesProviders.push(provider); }, - registerCapabilitiesSwitcher: (switcher: CapabilitiesSwitcher) => { + registerSwitcher: (switcher: CapabilitiesSwitcher) => { this.capabilitiesSwitchers.push(switcher); }, }; diff --git a/src/core/server/capabilities/integration_tests/capabilities_service.test.ts b/src/core/server/capabilities/integration_tests/capabilities_service.test.ts index 8d0e703723ed0..fe0f1e61bff45 100644 --- a/src/core/server/capabilities/integration_tests/capabilities_service.test.ts +++ b/src/core/server/capabilities/integration_tests/capabilities_service.test.ts @@ -97,7 +97,7 @@ describe('CapabilitiesService', () => { }); it('uses the service capabilities providers', async () => { - serviceSetup.registerCapabilitiesProvider(() => ({ + serviceSetup.registerProvider(() => ({ catalogue: { something: true, }, diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 9a9142ca8f4b7..154ab973c9464 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -103,8 +103,8 @@ export function createPluginSetupContext( ): CoreSetup { return { capabilities: { - registerCapabilitiesProvider: deps.capabilities.registerCapabilitiesProvider, - registerCapabilitiesSwitcher: deps.capabilities.registerCapabilitiesSwitcher, + registerProvider: deps.capabilities.registerProvider, + registerSwitcher: deps.capabilities.registerSwitcher, }, context: { createContextContainer: deps.context.createContextContainer, diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index da862b090acaa..70319fd60face 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -482,8 +482,8 @@ export type CapabilitiesProvider = () => Partial; // @public export interface CapabilitiesSetup { - registerCapabilitiesProvider(provider: CapabilitiesProvider): void; - registerCapabilitiesSwitcher(switcher: CapabilitiesSwitcher): void; + registerProvider(provider: CapabilitiesProvider): void; + registerSwitcher(switcher: CapabilitiesSwitcher): void; } // @public diff --git a/src/legacy/server/capabilities/capabilities_mixin.test.ts b/src/legacy/server/capabilities/capabilities_mixin.test.ts index ea4d60a85eb88..3422d6a8cbb34 100644 --- a/src/legacy/server/capabilities/capabilities_mixin.test.ts +++ b/src/legacy/server/capabilities/capabilities_mixin.test.ts @@ -33,7 +33,7 @@ describe('capabilitiesMixin', () => { setup: { core: { capabilities: { - registerCapabilitiesProvider: registerMock, + registerProvider: registerMock, }, }, }, diff --git a/src/legacy/server/capabilities/capabilities_mixin.ts b/src/legacy/server/capabilities/capabilities_mixin.ts index c833f6714f807..23a0c35414ae6 100644 --- a/src/legacy/server/capabilities/capabilities_mixin.ts +++ b/src/legacy/server/capabilities/capabilities_mixin.ts @@ -30,9 +30,7 @@ export async function capabilitiesMixin(kbnServer: KbnServer, server: Server) { ); capabilitiesList.forEach(capabilities => { - kbnServer.newPlatform.setup.core.capabilities.registerCapabilitiesProvider( - () => capabilities - ); + kbnServer.newPlatform.setup.core.capabilities.registerProvider(() => capabilities); }); }; diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index da5eff8583a24..bb9a530be1180 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -151,7 +151,7 @@ export class Plugin { featuresService: features, }); - core.capabilities.registerCapabilitiesSwitcher(authz.disableUnauthorizedCapabilities); + core.capabilities.registerSwitcher(authz.disableUnauthorizedCapabilities); defineRoutes({ router: core.http.createRouter(), diff --git a/x-pack/plugins/spaces/server/plugin.ts b/x-pack/plugins/spaces/server/plugin.ts index 66a1a3b3cfd36..c152f41e039a5 100644 --- a/x-pack/plugins/spaces/server/plugin.ts +++ b/x-pack/plugins/spaces/server/plugin.ts @@ -130,7 +130,7 @@ export class Plugin { features: plugins.features, }); - core.capabilities.registerCapabilitiesSwitcher(async (request, uiCapabilities) => { + core.capabilities.registerSwitcher(async (request, uiCapabilities) => { try { const activeSpace = await spacesService.getActiveSpace(request); const features = plugins.features.getFeatures(); From 997e47692b7a8fb870da46e45b685e46254a5343 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Wed, 27 Nov 2019 19:48:48 +0100 Subject: [PATCH 27/34] resolve conflicts due to merge --- .../server/kibana-plugin-server.corestart.md | 41 ++++++++++--------- src/core/server/mocks.ts | 1 + .../server/plugins/plugins_system.test.ts | 1 - src/core/server/server.ts | 3 +- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/docs/development/core/server/kibana-plugin-server.corestart.md b/docs/development/core/server/kibana-plugin-server.corestart.md index 3ca9d32b0b8a4..e523717a37ac8 100644 --- a/docs/development/core/server/kibana-plugin-server.corestart.md +++ b/docs/development/core/server/kibana-plugin-server.corestart.md @@ -1,20 +1,21 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreStart](./kibana-plugin-server.corestart.md) - -## CoreStart interface - -Context passed to the plugins `start` method. - -Signature: - -```typescript -export interface CoreStart -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [savedObjects](./kibana-plugin-server.corestart.savedobjects.md) | SavedObjectsServiceStart | [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) | - + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreStart](./kibana-plugin-server.corestart.md) + +## CoreStart interface + +Context passed to the plugins `start` method. + +Signature: + +```typescript +export interface CoreStart +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [capabilities](./kibana-plugin-server.corestart.capabilities.md) | CapabilitiesStart | [CapabilitiesStart](./kibana-plugin-server.capabilitiesstart.md) | +| [savedObjects](./kibana-plugin-server.corestart.savedobjects.md) | SavedObjectsServiceStart | [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) | + diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index dc56864afacee..025ebce22e2c4 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -121,6 +121,7 @@ function createInternalCoreSetupMock() { function createInternalCoreStartMock() { const startDeps: InternalCoreStart = { + capabilities: capabilitiesServiceMock.createStartContract(), savedObjects: savedObjectsServiceMock.createStartContract(), }; return startDeps; diff --git a/src/core/server/plugins/plugins_system.test.ts b/src/core/server/plugins/plugins_system.test.ts index 8ef24e014b08a..18c04af3bb641 100644 --- a/src/core/server/plugins/plugins_system.test.ts +++ b/src/core/server/plugins/plugins_system.test.ts @@ -29,7 +29,6 @@ import { getEnvOptions } from '../config/__mocks__/env'; import { CoreContext } from '../core_context'; import { configServiceMock } from '../config/config_service.mock'; import { loggingServiceMock } from '../logging/logging_service.mock'; -import { capabilitiesServiceMock } from '../capabilities/capabilities_service.mock'; import { PluginWrapper } from './plugin'; import { PluginName } from './types'; diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 05a4c88511fc5..555714ee57043 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -140,10 +140,9 @@ export class Server { const capabilitiesStart = this.capabilities.start(); const pluginsStart = await this.plugins.start({ capabilities: capabilitiesStart, + savedObjects: savedObjectsStart, }); - const pluginsStart = await this.plugins.start({ savedObjects: savedObjectsStart }); - const coreStart = { capabilities: capabilitiesStart, savedObjects: savedObjectsStart, From b37034a94988ea65a793517ade89d241cb026ec9 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Wed, 27 Nov 2019 20:22:59 +0100 Subject: [PATCH 28/34] address review issues --- .../capabilities/capabilities_service.test.ts | 8 +- .../capabilities/capabilities_service.test.ts | 95 ++++++++++++++++++- .../capabilities/capabilities_service.ts | 37 ++------ src/core/server/capabilities/routes/index.ts | 27 ++++++ .../routes/resolve_capabilities.ts | 47 +++++++++ 5 files changed, 182 insertions(+), 32 deletions(-) create mode 100644 src/core/server/capabilities/routes/index.ts create mode 100644 src/core/server/capabilities/routes/resolve_capabilities.ts diff --git a/src/core/public/application/capabilities/capabilities_service.test.ts b/src/core/public/application/capabilities/capabilities_service.test.ts index 7de0f3c26c98c..3245be8dd502d 100644 --- a/src/core/public/application/capabilities/capabilities_service.test.ts +++ b/src/core/public/application/capabilities/capabilities_service.test.ts @@ -45,6 +45,7 @@ describe('#start', () => { const apps = new Map([ ['app1', { id: 'app1' }], ['app2', { id: 'app2', capabilities: { app2: { feature: true } } }], + ['appMissingInCapabilities', { id: 'appMissingInCapabilities' }], ] as Array<[string, App]>); const legacyApps = new Map([ ['legacyApp1', { id: 'legacyApp1' }], @@ -54,7 +55,12 @@ describe('#start', () => { it('filters available apps based on returned navLinks', async () => { const service = new CapabilitiesService(); const startContract = await service.start({ apps, legacyApps, http }); - expect(startContract.availableApps).toEqual(new Map([['app1', { id: 'app1' }]])); + expect(startContract.availableApps).toEqual( + new Map([ + ['app1', { id: 'app1' }], + ['appMissingInCapabilities', { id: 'appMissingInCapabilities' }], + ]) + ); expect(startContract.availableLegacyApps).toEqual( new Map([['legacyApp1', { id: 'legacyApp1' }]]) ); diff --git a/src/core/server/capabilities/capabilities_service.test.ts b/src/core/server/capabilities/capabilities_service.test.ts index d47b914e36d6a..aace0b9debf9c 100644 --- a/src/core/server/capabilities/capabilities_service.test.ts +++ b/src/core/server/capabilities/capabilities_service.test.ts @@ -46,7 +46,7 @@ describe('CapabilitiesService', () => { expect(router.post).toHaveBeenCalledWith(expect.any(Object), expect.any(Function)); }); - it('allows to register capabilities providers', async () => { + it('allows to register a capabilities provider', async () => { setup.registerProvider(() => ({ navLinks: { myLink: true }, catalogue: { myPlugin: true }, @@ -65,6 +65,41 @@ describe('CapabilitiesService', () => { `); }); + it('allows to register multiple capabilities providers', async () => { + setup.registerProvider(() => ({ + navLinks: { A: true }, + catalogue: { A: true }, + })); + setup.registerProvider(() => ({ + navLinks: { B: true }, + catalogue: { B: true }, + })); + setup.registerProvider(() => ({ + navLinks: { C: true }, + customSection: { + C: true, + }, + })); + const start = service.start(); + expect(await start.resolveCapabilities({} as any)).toMatchInlineSnapshot(` + Object { + "catalogue": Object { + "A": true, + "B": true, + }, + "customSection": Object { + "C": true, + }, + "management": Object {}, + "navLinks": Object { + "A": true, + "B": true, + "C": true, + }, + } + `); + }); + it('allows to register capabilities switchers', async () => { setup.registerProvider(() => ({ catalogue: { a: true, b: true, c: true }, @@ -91,5 +126,63 @@ describe('CapabilitiesService', () => { } `); }); + + it('allows to register multiple providers and switchers', async () => { + setup.registerProvider(() => ({ + navLinks: { a: true }, + catalogue: { a: true }, + })); + setup.registerProvider(() => ({ + navLinks: { b: true }, + catalogue: { b: true }, + })); + setup.registerProvider(() => ({ + navLinks: { c: true }, + catalogue: { c: true }, + customSection: { + c: true, + }, + })); + setup.registerSwitcher((req, capabilities) => { + return { + catalogue: { + b: false, + }, + }; + }); + + setup.registerSwitcher((req, capabilities) => { + return { + navLinks: { c: false }, + }; + }); + setup.registerSwitcher((req, capabilities) => { + return { + customSection: { + c: false, + }, + }; + }); + + const start = service.start(); + expect(await start.resolveCapabilities({} as any)).toMatchInlineSnapshot(` + Object { + "catalogue": Object { + "a": true, + "b": false, + "c": true, + }, + "customSection": Object { + "c": false, + }, + "management": Object {}, + "navLinks": Object { + "a": true, + "b": true, + "c": false, + }, + } + `); + }); }); }); diff --git a/src/core/server/capabilities/capabilities_service.ts b/src/core/server/capabilities/capabilities_service.ts index 985bb1175800f..e14cae2c531f9 100644 --- a/src/core/server/capabilities/capabilities_service.ts +++ b/src/core/server/capabilities/capabilities_service.ts @@ -17,13 +17,13 @@ * under the License. */ -import { schema } from '@kbn/config-schema'; import { Capabilities, CapabilitiesProvider, CapabilitiesSwitcher } from './types'; import { CoreContext } from '../core_context'; import { Logger } from '../logging'; import { InternalHttpServiceSetup, KibanaRequest } from '../http'; import { mergeCapabilities } from './merge_capabilities'; import { getCapabilitiesResolver, CapabilitiesResolver } from './resolve_capabilities'; +import { registerRoutes } from './routes'; /** * APIs to manage the {@link Capabilities} that will be used by the application. @@ -32,7 +32,8 @@ import { getCapabilitiesResolver, CapabilitiesResolver } from './resolve_capabil */ export interface CapabilitiesSetup { /** - * Register a {@link CapabilitiesProvider} to be used when resolving capabilities. + * Register a {@link CapabilitiesProvider} to be used to provide {@link Capabilities} + * when resolving them. * * @example * ```ts @@ -54,7 +55,8 @@ export interface CapabilitiesSetup { registerProvider(provider: CapabilitiesProvider): void; /** - * Register a {@link CapabilitiesSwitcher} to be used when resolving capabilities. + * Register a {@link CapabilitiesSwitcher} to be used to change the default state + * of the {@link Capabilities} entries when resolving them. * * @example * ```ts @@ -119,7 +121,8 @@ export class CapabilitiesService { public setup(setupDeps: SetupDeps): CapabilitiesSetup { this.logger.debug('Setting up capabilities service'); - this.setupCapabilitiesRoute(setupDeps.http); + + registerRoutes(setupDeps.http, this.resolveCapabilities); return { registerProvider: (provider: CapabilitiesProvider) => { @@ -136,30 +139,4 @@ export class CapabilitiesService { resolveCapabilities: request => this.resolveCapabilities(request, []), }; } - - private setupCapabilitiesRoute(http: InternalHttpServiceSetup) { - const router = http.createRouter('/api/core/capabilities'); - [true, false].forEach(authRequired => { - router.post( - { - path: authRequired ? '' : '/defaults', - options: { - authRequired, - }, - validate: { - body: schema.object({ - applications: schema.arrayOf(schema.string()), - }), - }, - }, - async (ctx, req, res) => { - const { applications } = req.body; - const capabilities = await this.resolveCapabilities(req, applications); - return res.ok({ - body: capabilities, - }); - } - ); - }); - } } diff --git a/src/core/server/capabilities/routes/index.ts b/src/core/server/capabilities/routes/index.ts new file mode 100644 index 0000000000000..ccaa4621d7003 --- /dev/null +++ b/src/core/server/capabilities/routes/index.ts @@ -0,0 +1,27 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { CapabilitiesResolver } from '../resolve_capabilities'; +import { InternalHttpServiceSetup } from '../../http'; +import { registerCapabilitiesRoutes } from './resolve_capabilities'; + +export function registerRoutes(http: InternalHttpServiceSetup, resolver: CapabilitiesResolver) { + const router = http.createRouter('/api/core/capabilities'); + registerCapabilitiesRoutes(router, resolver); +} diff --git a/src/core/server/capabilities/routes/resolve_capabilities.ts b/src/core/server/capabilities/routes/resolve_capabilities.ts new file mode 100644 index 0000000000000..507eaafb59a16 --- /dev/null +++ b/src/core/server/capabilities/routes/resolve_capabilities.ts @@ -0,0 +1,47 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { schema } from '@kbn/config-schema'; +import { IRouter } from '../../http'; +import { CapabilitiesResolver } from '../resolve_capabilities'; + +export function registerCapabilitiesRoutes(router: IRouter, resolver: CapabilitiesResolver) { + [true, false].forEach(authRequired => { + router.post( + { + path: authRequired ? '' : '/defaults', + options: { + authRequired, + }, + validate: { + body: schema.object({ + applications: schema.arrayOf(schema.string()), + }), + }, + }, + async (ctx, req, res) => { + const { applications } = req.body; + const capabilities = await resolver(req, applications); + return res.ok({ + body: capabilities, + }); + } + ); + }); +} From ebcfddeb0695eef163b342e1db3127c565146397 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Fri, 29 Nov 2019 14:26:31 +0100 Subject: [PATCH 29/34] add comment about reason for exposing two routes --- src/core/server/capabilities/routes/resolve_capabilities.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/core/server/capabilities/routes/resolve_capabilities.ts b/src/core/server/capabilities/routes/resolve_capabilities.ts index 507eaafb59a16..5e1d49b4b1b7e 100644 --- a/src/core/server/capabilities/routes/resolve_capabilities.ts +++ b/src/core/server/capabilities/routes/resolve_capabilities.ts @@ -22,6 +22,10 @@ import { IRouter } from '../../http'; import { CapabilitiesResolver } from '../resolve_capabilities'; export function registerCapabilitiesRoutes(router: IRouter, resolver: CapabilitiesResolver) { + // Capabilities are fetched on both authenticated and anonymous routes. + // However when `authRequired` is false, authentication is not performed + // and only default capabilities are returned (all disabled), even for authenticated users. + // So we need two endpoints to handle both scenarios. [true, false].forEach(authRequired => { router.post( { From ad0ffbb5d9377b5db6b45f7360d45c7bb3837c5e Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Fri, 29 Nov 2019 15:14:39 +0100 Subject: [PATCH 30/34] extract createHttpServer test helper --- .eslintrc.js | 1 + .../capabilities_service.test.ts | 28 +-------- .../http/integration_tests/lifecycle.test.ts | 26 +------- .../http/integration_tests/router.test.ts | 26 +------- src/core/server/http/test_utils.ts | 62 +++++++++++++++++++ src/core/server/test_utils.ts | 20 ++++++ 6 files changed, 90 insertions(+), 73 deletions(-) create mode 100644 src/core/server/http/test_utils.ts create mode 100644 src/core/server/test_utils.ts diff --git a/.eslintrc.js b/.eslintrc.js index fe546ec02a668..f4f00a04517cf 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -337,6 +337,7 @@ module.exports = { '!src/core/server/index.ts', '!src/core/server/mocks.ts', '!src/core/server/types.ts', + '!src/core/server/test_utils.ts', // for absolute imports until fixed in // https://github.com/elastic/kibana/issues/36096 '!src/core/server/types', diff --git a/src/core/server/capabilities/integration_tests/capabilities_service.test.ts b/src/core/server/capabilities/integration_tests/capabilities_service.test.ts index fe0f1e61bff45..6be9846f5a86a 100644 --- a/src/core/server/capabilities/integration_tests/capabilities_service.test.ts +++ b/src/core/server/capabilities/integration_tests/capabilities_service.test.ts @@ -17,44 +17,18 @@ * under the License. */ -import { BehaviorSubject } from 'rxjs'; -import { ByteSizeValue } from '@kbn/config-schema'; import supertest from 'supertest'; - import { HttpService, InternalHttpServiceSetup } from '../../http'; -import { configServiceMock } from '../../config/config_service.mock'; import { contextServiceMock } from '../../context/context_service.mock'; import { loggingServiceMock } from '../../logging/logging_service.mock'; import { Env } from '../../config'; import { getEnvOptions } from '../../config/__mocks__/env'; import { CapabilitiesService, CapabilitiesSetup } from '..'; +import { createHttpServer } from '../../http/test_utils'; const coreId = Symbol('core'); const env = Env.createDefault(getEnvOptions()); -const createHttpServer = (): HttpService => { - const logger = loggingServiceMock.create(); - - const configService = configServiceMock.create(); - configService.atPath.mockReturnValue( - new BehaviorSubject({ - hosts: ['localhost'], - maxPayload: new ByteSizeValue(1024), - autoListen: true, - ssl: { - enabled: false, - }, - } as any) - ); - const coreContext = { - coreId, - env, - logger, - configService: configService as any, - }; - return new HttpService(coreContext); -}; - describe('CapabilitiesService', () => { let server: HttpService; let httpSetup: InternalHttpServiceSetup; diff --git a/src/core/server/http/integration_tests/lifecycle.test.ts b/src/core/server/http/integration_tests/lifecycle.test.ts index 7c4a0097456ca..2a32db77377a4 100644 --- a/src/core/server/http/integration_tests/lifecycle.test.ts +++ b/src/core/server/http/integration_tests/lifecycle.test.ts @@ -18,48 +18,28 @@ */ import supertest from 'supertest'; -import { ByteSizeValue } from '@kbn/config-schema'; import request from 'request'; -import { BehaviorSubject } from 'rxjs'; import { ensureRawRequest } from '../router'; import { HttpService } from '../http_service'; -import { CoreContext } from '../../core_context'; -import { Env } from '../../config'; -import { getEnvOptions } from '../../config/__mocks__/env'; -import { configServiceMock } from '../../config/config_service.mock'; import { contextServiceMock } from '../../context/context_service.mock'; import { loggingServiceMock } from '../../logging/logging_service.mock'; +import { createHttpServer } from '../test_utils'; let server: HttpService; let logger: ReturnType; -let env: Env; -let coreContext: CoreContext; -const configService = configServiceMock.create(); + const contextSetup = contextServiceMock.createSetupContract(); const setupDeps = { context: contextSetup, }; -configService.atPath.mockReturnValue( - new BehaviorSubject({ - hosts: ['localhost'], - maxPayload: new ByteSizeValue(1024), - autoListen: true, - ssl: { - enabled: false, - }, - } as any) -); beforeEach(() => { logger = loggingServiceMock.create(); - env = Env.createDefault(getEnvOptions()); - - coreContext = { coreId: Symbol('core'), env, logger, configService: configService as any }; - server = new HttpService(coreContext); + server = createHttpServer({ logger }); }); afterEach(async () => { diff --git a/src/core/server/http/integration_tests/router.test.ts b/src/core/server/http/integration_tests/router.test.ts index 481d8e1bbf49b..7d95110b98a12 100644 --- a/src/core/server/http/integration_tests/router.test.ts +++ b/src/core/server/http/integration_tests/router.test.ts @@ -18,48 +18,28 @@ */ import { Stream } from 'stream'; import Boom from 'boom'; - import supertest from 'supertest'; -import { BehaviorSubject } from 'rxjs'; -import { ByteSizeValue, schema } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; import { HttpService } from '../http_service'; -import { CoreContext } from '../../core_context'; -import { Env } from '../../config'; -import { getEnvOptions } from '../../config/__mocks__/env'; -import { configServiceMock } from '../../config/config_service.mock'; import { contextServiceMock } from '../../context/context_service.mock'; import { loggingServiceMock } from '../../logging/logging_service.mock'; +import { createHttpServer } from '../test_utils'; let server: HttpService; let logger: ReturnType; -let env: Env; -let coreContext: CoreContext; -const configService = configServiceMock.create(); const contextSetup = contextServiceMock.createSetupContract(); const setupDeps = { context: contextSetup, }; -configService.atPath.mockReturnValue( - new BehaviorSubject({ - hosts: ['localhost'], - maxPayload: new ByteSizeValue(1024), - autoListen: true, - ssl: { - enabled: false, - }, - } as any) -); beforeEach(() => { logger = loggingServiceMock.create(); - env = Env.createDefault(getEnvOptions()); - coreContext = { coreId: Symbol('core'), env, logger, configService: configService as any }; - server = new HttpService(coreContext); + server = createHttpServer({ logger }); }); afterEach(async () => { diff --git a/src/core/server/http/test_utils.ts b/src/core/server/http/test_utils.ts new file mode 100644 index 0000000000000..a7c9a2b0e8fb7 --- /dev/null +++ b/src/core/server/http/test_utils.ts @@ -0,0 +1,62 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { BehaviorSubject } from 'rxjs'; +import { ByteSizeValue } from '@kbn/config-schema'; +import { Env } from '../config'; +import { getEnvOptions } from '../config/__mocks__/env'; +import { HttpService } from './http_service'; +import { CoreContext } from '../core_context'; +import { configServiceMock } from '../config/config_service.mock'; +import { loggingServiceMock } from '../logging/logging_service.mock'; + +const coreId = Symbol('core'); +const env = Env.createDefault(getEnvOptions()); + +const logger = loggingServiceMock.create(); + +const configService = configServiceMock.create(); +configService.atPath.mockReturnValue( + new BehaviorSubject({ + hosts: ['localhost'], + maxPayload: new ByteSizeValue(1024), + autoListen: true, + ssl: { + enabled: false, + }, + } as any) +); + +const defaultContext: CoreContext = { + coreId, + env, + logger, + configService, +}; + +/** + * Creates a concrete HttpServer with a mocked context. + */ +export const createHttpServer = (overrides: Partial = {}): HttpService => { + const context = { + ...defaultContext, + ...overrides, + }; + return new HttpService(context); +}; diff --git a/src/core/server/test_utils.ts b/src/core/server/test_utils.ts new file mode 100644 index 0000000000000..470b1c2d135b7 --- /dev/null +++ b/src/core/server/test_utils.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { createHttpServer } from './http/test_utils'; From d705f694317089f8567a42c4ae033cb7d6cc3f5a Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Fri, 29 Nov 2019 15:53:12 +0100 Subject: [PATCH 31/34] fix merge conflicts --- src/core/server/http/router/router.mock.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/server/http/router/router.mock.ts b/src/core/server/http/router/router.mock.ts index 9446dee0e5186..b43db0ca7ed5a 100644 --- a/src/core/server/http/router/router.mock.ts +++ b/src/core/server/http/router/router.mock.ts @@ -28,6 +28,7 @@ function create({ routerPath = '' }: { routerPath?: string } = {}): RouterMock { post: jest.fn(), delete: jest.fn(), put: jest.fn(), + patch: jest.fn(), getRoutes: jest.fn(), handleLegacyErrors: jest.fn().mockImplementation(handler => handler), }; From 7cb55131b5d476c1e8665e3cbeceb41bc89908db Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Mon, 2 Dec 2019 12:06:40 +0100 Subject: [PATCH 32/34] improve documentation --- .../kibana-plugin-server.capabilitiessetup.md | 10 ++++-- ...rver.capabilitiessetup.registerprovider.md | 8 +++-- ...rver.capabilitiessetup.registerswitcher.md | 21 +++++++----- .../core/server/kibana-plugin-server.md | 2 +- .../capabilities/capabilities_service.ts | 34 +++++++++++++------ 5 files changed, 50 insertions(+), 25 deletions(-) diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.md b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.md index 64cc7ed7da2fb..27c42fe75e751 100644 --- a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.md +++ b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.md @@ -6,6 +6,12 @@ APIs to manage the [Capabilities](./kibana-plugin-server.capabilities.md) that will be used by the application. +Plugins relying on capabilities to toggle some of their features should register them during the setup phase using the `registerProvider` method. + +Plugins having the responsibility to restrict capabilities depending on a given context should register their capabilities switcher using the `registerSwitcher` method. + +Refers to the methods documentation for complete description and examples. + Signature: ```typescript @@ -16,6 +22,6 @@ export interface CapabilitiesSetup | Method | Description | | --- | --- | -| [registerProvider(provider)](./kibana-plugin-server.capabilitiessetup.registerprovider.md) | Register a [CapabilitiesProvider](./kibana-plugin-server.capabilitiesprovider.md) to be used when resolving capabilities. | -| [registerSwitcher(switcher)](./kibana-plugin-server.capabilitiessetup.registerswitcher.md) | Register a [CapabilitiesSwitcher](./kibana-plugin-server.capabilitiesswitcher.md) to be used when resolving capabilities. | +| [registerProvider(provider)](./kibana-plugin-server.capabilitiessetup.registerprovider.md) | Register a [CapabilitiesProvider](./kibana-plugin-server.capabilitiesprovider.md) to be used to provide [Capabilities](./kibana-plugin-server.capabilities.md) when resolving them. | +| [registerSwitcher(switcher)](./kibana-plugin-server.capabilitiessetup.registerswitcher.md) | Register a [CapabilitiesSwitcher](./kibana-plugin-server.capabilitiesswitcher.md) to be used to change the default state of the [Capabilities](./kibana-plugin-server.capabilities.md) entries when resolving them.A capabilities switcher can only change the state of existing capabilities. Capabilities added or removed when invoking the switcher will be ignored. | diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registerprovider.md b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registerprovider.md index 0148ae377fd71..750913ee35895 100644 --- a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registerprovider.md +++ b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registerprovider.md @@ -4,7 +4,7 @@ ## CapabilitiesSetup.registerProvider() method -Register a [CapabilitiesProvider](./kibana-plugin-server.capabilitiesprovider.md) to be used when resolving capabilities. +Register a [CapabilitiesProvider](./kibana-plugin-server.capabilitiesprovider.md) to be used to provide [Capabilities](./kibana-plugin-server.capabilities.md) when resolving them. Signature: @@ -24,6 +24,7 @@ registerProvider(provider: CapabilitiesProvider): void; ## Example +How to register a plugin's capabilities during setup ```ts // my-plugin/server/plugin.ts @@ -34,10 +35,11 @@ public setup(core: CoreSetup, deps: {}) { myPlugin: true, }, myPlugin: { - feature: true, + someFeature: true, + featureDisabledByDefault: false, }, } - }) + }); } ``` diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registerswitcher.md b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registerswitcher.md index aeefa0e2bface..fbaa2959c635c 100644 --- a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registerswitcher.md +++ b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registerswitcher.md @@ -4,7 +4,9 @@ ## CapabilitiesSetup.registerSwitcher() method -Register a [CapabilitiesSwitcher](./kibana-plugin-server.capabilitiesswitcher.md) to be used when resolving capabilities. +Register a [CapabilitiesSwitcher](./kibana-plugin-server.capabilitiesswitcher.md) to be used to change the default state of the [Capabilities](./kibana-plugin-server.capabilities.md) entries when resolving them. + +A capabilities switcher can only change the state of existing capabilities. Capabilities added or removed when invoking the switcher will be ignored. Signature: @@ -22,22 +24,23 @@ registerSwitcher(switcher: CapabilitiesSwitcher): void; `void` -## Remarks - -A capabilities switcher can only change the state of existing capabilities. capabilities added or removed when invoking the switcher will be ignored. - ## Example +How to restrict some capabilities ```ts // my-plugin/server/plugin.ts public setup(core: CoreSetup, deps: {}) { core.capabilities.registerSwitcher((request, capabilities) => { - if(myPluginApi.shouldRestrictBecauseOf(request)) { - return myPluginApi.disableSomeCapabilities(capabilities); + if(myPluginApi.shouldRestrictSomePluginBecauseOf(request)) { + return { + somePlugin: { + featureEnabledByDefault: false // `featureEnabledByDefault` will be disabled. All other capabilities will remain unchanged. + } + } } - return capabilities; - }) + return {}; // All capabilities will remain unchanged. + }); } ``` diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 2150be0f07797..9144742c9bb73 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -44,7 +44,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [AuthToolkit](./kibana-plugin-server.authtoolkit.md) | A tool set defining an outcome of Auth interceptor for incoming request. | | [CallAPIOptions](./kibana-plugin-server.callapioptions.md) | The set of options that defines how API call should be made and result be processed. | | [Capabilities](./kibana-plugin-server.capabilities.md) | The read-only set of capabilities available for the current UI session. Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, and the boolean is a flag indicating if the capability is enabled or disabled. | -| [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | APIs to manage the [Capabilities](./kibana-plugin-server.capabilities.md) that will be used by the application. | +| [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | APIs to manage the [Capabilities](./kibana-plugin-server.capabilities.md) that will be used by the application.Plugins relying on capabilities to toggle some of their features should register them during the setup phase using the registerProvider method.Plugins having the responsibility to restrict capabilities depending on a given context should register their capabilities switcher using the registerSwitcher method.Refers to the methods documentation for complete description and examples. | | [CapabilitiesStart](./kibana-plugin-server.capabilitiesstart.md) | APIs to access the application [Capabilities](./kibana-plugin-server.capabilities.md). | | [ContextSetup](./kibana-plugin-server.contextsetup.md) | An object that handles registration of context providers and configuring handlers with context. | | [CoreSetup](./kibana-plugin-server.coresetup.md) | Context passed to the plugins setup method. | diff --git a/src/core/server/capabilities/capabilities_service.ts b/src/core/server/capabilities/capabilities_service.ts index e14cae2c531f9..d3d6b507da523 100644 --- a/src/core/server/capabilities/capabilities_service.ts +++ b/src/core/server/capabilities/capabilities_service.ts @@ -28,6 +28,14 @@ import { registerRoutes } from './routes'; /** * APIs to manage the {@link Capabilities} that will be used by the application. * + * Plugins relying on capabilities to toggle some of their features should register them during the setup phase + * using the `registerProvider` method. + * + * Plugins having the responsibility to restrict capabilities depending on a given context should register + * their capabilities switcher using the `registerSwitcher` method. + * + * Refers to the methods documentation for complete description and examples. + * * @public */ export interface CapabilitiesSetup { @@ -36,6 +44,7 @@ export interface CapabilitiesSetup { * when resolving them. * * @example + * How to register a plugin's capabilities during setup * ```ts * // my-plugin/server/plugin.ts * public setup(core: CoreSetup, deps: {}) { @@ -45,10 +54,11 @@ export interface CapabilitiesSetup { * myPlugin: true, * }, * myPlugin: { - * feature: true, + * someFeature: true, + * featureDisabledByDefault: false, * }, * } - * }) + * }); * } * ``` */ @@ -58,22 +68,26 @@ export interface CapabilitiesSetup { * Register a {@link CapabilitiesSwitcher} to be used to change the default state * of the {@link Capabilities} entries when resolving them. * + * A capabilities switcher can only change the state of existing capabilities. + * Capabilities added or removed when invoking the switcher will be ignored. + * * @example + * How to restrict some capabilities * ```ts * // my-plugin/server/plugin.ts * public setup(core: CoreSetup, deps: {}) { * core.capabilities.registerSwitcher((request, capabilities) => { - * if(myPluginApi.shouldRestrictBecauseOf(request)) { - * return myPluginApi.disableSomeCapabilities(capabilities); + * if(myPluginApi.shouldRestrictSomePluginBecauseOf(request)) { + * return { + * somePlugin: { + * featureEnabledByDefault: false // `featureEnabledByDefault` will be disabled. All other capabilities will remain unchanged. + * } + * } * } - * return capabilities; - * }) + * return {}; // All capabilities will remain unchanged. + * }); * } * ``` - * - * @remarks - * A capabilities switcher can only change the state of existing capabilities. - * capabilities added or removed when invoking the switcher will be ignored. */ registerSwitcher(switcher: CapabilitiesSwitcher): void; } From dbf5131da50b7c55bc899cc3e79e348d26062ab1 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Mon, 2 Dec 2019 17:29:28 +0100 Subject: [PATCH 33/34] remove `/status` anon registration as now done in NP status plugin --- x-pack/plugins/security/public/plugin.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/security/public/plugin.ts b/x-pack/plugins/security/public/plugin.ts index ae2c910bce036..7b1a554e1d3f1 100644 --- a/x-pack/plugins/security/public/plugin.ts +++ b/x-pack/plugins/security/public/plugin.ts @@ -21,7 +21,6 @@ export class SecurityPlugin implements Plugin Date: Tue, 3 Dec 2019 08:15:47 +0100 Subject: [PATCH 34/34] fix merge conflicts --- src/core/server/http/test_utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/server/http/test_utils.ts b/src/core/server/http/test_utils.ts index a7c9a2b0e8fb7..e0a15cdc6e839 100644 --- a/src/core/server/http/test_utils.ts +++ b/src/core/server/http/test_utils.ts @@ -40,6 +40,7 @@ configService.atPath.mockReturnValue( ssl: { enabled: false, }, + compression: { enabled: true }, } as any) );