diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index e834e19fc77d8..26e6469a4c6b4 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -23,6 +23,7 @@ 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'; type ServiceSetupMockType = jest.Mocked & { basePath: jest.Mocked; @@ -89,6 +90,10 @@ const createOnPreAuthToolkitMock = (): jest.Mocked => ({ rewriteUrl: jest.fn(), }); +const createOnPostAuthToolkitMock = (): jest.Mocked => ({ + next: jest.fn(), +}); + const createAuthToolkitMock = (): jest.Mocked => ({ authenticated: jest.fn(), }); @@ -98,6 +103,7 @@ export const httpServiceMock = { createBasePath: createBasePathMock, createSetupContract: createSetupContractMock, createOnPreAuthToolkit: createOnPreAuthToolkitMock, + createOnPostAuthToolkit: createOnPostAuthToolkitMock, createAuthToolkit: createAuthToolkitMock, createRouter: createRouterMock, }; diff --git a/x-pack/legacy/plugins/spaces/index.ts b/x-pack/legacy/plugins/spaces/index.ts index f7c78ca8369a8..b56fe3e5e618a 100644 --- a/x-pack/legacy/plugins/spaces/index.ts +++ b/x-pack/legacy/plugins/spaces/index.ts @@ -7,6 +7,7 @@ import * as Rx from 'rxjs'; import { resolve } from 'path'; import KbnServer, { Server } from 'src/legacy/server/kbn_server'; +import { CoreSetup, PluginInitializerContext } from 'src/core/server'; import { createOptionalPlugin } from '../../server/lib/optional_plugin'; // @ts-ignore import { AuditLogger } from '../../server/lib/audit_logger'; @@ -16,14 +17,9 @@ import { getActiveSpace } from './server/lib/get_active_space'; import { getSpaceSelectorUrl } from './server/lib/get_space_selector_url'; import { migrateToKibana660 } from './server/lib/migrations'; import { plugin } from './server/new_platform'; -import { - SpacesInitializerContext, - SpacesCoreSetup, - SpacesHttpServiceSetup, -} from './server/new_platform/plugin'; -import { initSpacesRequestInterceptors } from './server/lib/request_interceptors'; import { SecurityPlugin } from '../security'; import { SpacesServiceSetup } from './server/new_platform/spaces_service/spaces_service'; +import { initSpaceSelectorView } from './server/routes/views'; export interface SpacesPlugin { getSpaceId: SpacesServiceSetup['getSpaceId']; @@ -122,8 +118,7 @@ export const spaces = (kibana: Record) => async init(server: Server) { const kbnServer = (server as unknown) as KbnServer; - const initializerContext = ({ - legacyConfig: server.config(), + const initializerContext = { config: { create: () => { return Rx.of({ @@ -140,29 +135,9 @@ export const spaces = (kibana: Record) => ); }, }, - } as unknown) as SpacesInitializerContext; + } as PluginInitializerContext; - const spacesHttpService: SpacesHttpServiceSetup = { - ...kbnServer.newPlatform.setup.core.http, - route: server.route.bind(server), - }; - - const core: SpacesCoreSetup = { - http: spacesHttpService, - elasticsearch: kbnServer.newPlatform.setup.core.elasticsearch, - savedObjects: server.savedObjects, - usage: server.usage, - tutorial: { - addScopedTutorialContextFactory: server.addScopedTutorialContextFactory, - }, - capabilities: { - registerCapabilitiesModifier: server.registerCapabilitiesModifier, - }, - auditLogger: { - create: (pluginId: string) => - new AuditLogger(server, pluginId, server.config(), server.plugins.xpack_main.info), - }, - }; + const core = (kbnServer.newPlatform.setup.core as unknown) as CoreSetup; const plugins = { xpackMain: server.plugins.xpack_main, @@ -177,20 +152,36 @@ export const spaces = (kibana: Record) => spaces: this, }; - const { spacesService, log } = await plugin(initializerContext).setup(core, plugins); + const { spacesService, registerLegacyAPI } = await plugin(initializerContext).setup( + core, + plugins + ); - initSpacesRequestInterceptors({ - config: initializerContext.legacyConfig, - http: core.http, - getHiddenUiAppById: server.getHiddenUiAppById, - onPostAuth: handler => { - server.ext('onPostAuth', handler); + const config = server.config(); + + registerLegacyAPI({ + router: server.route.bind(server), + legacyConfig: { + serverBasePath: config.get('server.basePath'), + serverDefaultRoute: config.get('server.defaultRoute'), + kibanaIndex: config.get('kibana.index'), + }, + savedObjects: server.savedObjects, + usage: server.usage, + tutorial: { + addScopedTutorialContextFactory: server.addScopedTutorialContextFactory, + }, + capabilities: { + registerCapabilitiesModifier: server.registerCapabilitiesModifier, + }, + auditLogger: { + create: (pluginId: string) => + new AuditLogger(server, pluginId, server.config(), server.plugins.xpack_main.info), }, - log, - spacesService, - xpackMain: plugins.xpackMain, }); + initSpaceSelectorView(server); + server.expose('getSpaceId', (request: any) => spacesService.getSpaceId(request)); server.expose('spaceIdToNamespace', spacesService.spaceIdToNamespace); server.expose('namespaceToSpaceId', spacesService.namespaceToSpaceId); diff --git a/x-pack/legacy/plugins/spaces/server/lib/create_default_space.ts b/x-pack/legacy/plugins/spaces/server/lib/create_default_space.ts index 81e391c8f5710..bde9a5132182b 100644 --- a/x-pack/legacy/plugins/spaces/server/lib/create_default_space.ts +++ b/x-pack/legacy/plugins/spaces/server/lib/create_default_space.ts @@ -7,11 +7,11 @@ import { i18n } from '@kbn/i18n'; import { first } from 'rxjs/operators'; -import { ElasticsearchServiceSetup, SavedObjectsService } from 'src/core/server'; +import { SavedObjectsService, CoreSetup } from 'src/core/server'; import { DEFAULT_SPACE_ID } from '../../common/constants'; interface Deps { - elasticsearch: ElasticsearchServiceSetup; + elasticsearch: CoreSetup['elasticsearch']; savedObjects: SavedObjectsService; } diff --git a/x-pack/legacy/plugins/spaces/server/lib/errors.ts b/x-pack/legacy/plugins/spaces/server/lib/errors.ts index de4b09206d3ee..4d8d71dca7af6 100644 --- a/x-pack/legacy/plugins/spaces/server/lib/errors.ts +++ b/x-pack/legacy/plugins/spaces/server/lib/errors.ts @@ -3,11 +3,11 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore -import { boomify } from 'boom'; + +import { boomify, isBoom } from 'boom'; export function wrapError(error: any) { - if (error.isBoom) { + if (isBoom(error)) { return error; } diff --git a/x-pack/legacy/plugins/spaces/server/lib/get_space_selector_url.test.ts b/x-pack/legacy/plugins/spaces/server/lib/get_space_selector_url.test.ts index 77d7db1328c14..b27d119a0d310 100644 --- a/x-pack/legacy/plugins/spaces/server/lib/get_space_selector_url.test.ts +++ b/x-pack/legacy/plugins/spaces/server/lib/get_space_selector_url.test.ts @@ -6,25 +6,14 @@ import { getSpaceSelectorUrl } from './get_space_selector_url'; -const buildServerConfig = (serverBasePath?: string) => { - return { - get: (key: string) => { - if (key === 'server.basePath') { - return serverBasePath; - } - throw new Error(`unexpected config request: ${key}`); - }, - }; -}; - describe('getSpaceSelectorUrl', () => { it('returns / when no server base path is defined', () => { - const serverConfig = buildServerConfig(); - expect(getSpaceSelectorUrl(serverConfig)).toEqual('/'); + expect(getSpaceSelectorUrl('')).toEqual('/spaces/space_selector'); }); it('returns the server base path when defined', () => { - const serverConfig = buildServerConfig('/my/server/base/path'); - expect(getSpaceSelectorUrl(serverConfig)).toEqual('/my/server/base/path'); + expect(getSpaceSelectorUrl('/my/server/base/path')).toEqual( + '/my/server/base/path/spaces/space_selector' + ); }); }); diff --git a/x-pack/legacy/plugins/spaces/server/lib/get_space_selector_url.ts b/x-pack/legacy/plugins/spaces/server/lib/get_space_selector_url.ts index 3f24553306b2b..6d088fda757de 100644 --- a/x-pack/legacy/plugins/spaces/server/lib/get_space_selector_url.ts +++ b/x-pack/legacy/plugins/spaces/server/lib/get_space_selector_url.ts @@ -4,10 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -interface Config { - get(key: string): string | boolean | number | null | undefined; -} - -export function getSpaceSelectorUrl(serverConfig: Config) { - return serverConfig.get('server.basePath') || '/'; +export function getSpaceSelectorUrl(serverBasePath: string = '') { + return `${serverBasePath}/spaces/space_selector`; } diff --git a/x-pack/legacy/plugins/spaces/server/lib/get_spaces_usage_collector.test.ts b/x-pack/legacy/plugins/spaces/server/lib/get_spaces_usage_collector.test.ts index 9096a19a24d06..6609ca42a7f67 100644 --- a/x-pack/legacy/plugins/spaces/server/lib/get_spaces_usage_collector.test.ts +++ b/x-pack/legacy/plugins/spaces/server/lib/get_spaces_usage_collector.test.ts @@ -43,13 +43,6 @@ function getServerMock(customization?: any) { log: () => { return; }, - config: () => ({ - get: (key: string) => { - if (key === 'xpack.spaces.enabled') { - return true; - } - }, - }), usage: { collectorSet: { makeUsageCollector: (options: any) => { @@ -79,24 +72,6 @@ const defaultCallClusterMock = jest.fn().mockResolvedValue({ }, }); -test('sets enabled to false when spaces is turned off', async () => { - const mockConfigGet = jest.fn(key => { - if (key === 'xpack.spaces.enabled') { - return false; - } else if (key.indexOf('xpack.spaces') >= 0) { - throw new Error('Unknown config key!'); - } - }); - const serverMock = getServerMock({ config: () => ({ get: mockConfigGet }) }); - const { fetch: getSpacesUsage } = getSpacesUsageCollector({ - config: serverMock.config(), - usage: serverMock.usage, - xpackMain: serverMock.plugins.xpack_main, - }); - const usageStats: UsageStats = await getSpacesUsage(defaultCallClusterMock); - expect(usageStats.enabled).toBe(false); -}); - describe('with a basic license', () => { let serverWithBasicLicenseMock: any; let usageStats: UsageStats; @@ -106,7 +81,7 @@ describe('with a basic license', () => { .fn() .mockReturnValue('basic'); const { fetch: getSpacesUsage } = getSpacesUsageCollector({ - config: serverWithBasicLicenseMock.config(), + kibanaIndex: '.kibana', usage: serverWithBasicLicenseMock.usage, xpackMain: serverWithBasicLicenseMock.plugins.xpack_main, }); @@ -142,7 +117,7 @@ describe('with no license', () => { serverWithNoLicenseMock.plugins.xpack_main.info.isAvailable = jest.fn().mockReturnValue(false); const { fetch: getSpacesUsage } = getSpacesUsageCollector({ - config: serverWithNoLicenseMock.config(), + kibanaIndex: '.kibana', usage: serverWithNoLicenseMock.usage, xpackMain: serverWithNoLicenseMock.plugins.xpack_main, }); @@ -175,7 +150,7 @@ describe('with platinum license', () => { .fn() .mockReturnValue('platinum'); const { fetch: getSpacesUsage } = getSpacesUsageCollector({ - config: serverWithPlatinumLicenseMock.config(), + kibanaIndex: '.kibana', usage: serverWithPlatinumLicenseMock.usage, xpackMain: serverWithPlatinumLicenseMock.plugins.xpack_main, }); diff --git a/x-pack/legacy/plugins/spaces/server/lib/get_spaces_usage_collector.ts b/x-pack/legacy/plugins/spaces/server/lib/get_spaces_usage_collector.ts index f4e06116fabd8..623f613faaa0c 100644 --- a/x-pack/legacy/plugins/spaces/server/lib/get_spaces_usage_collector.ts +++ b/x-pack/legacy/plugins/spaces/server/lib/get_spaces_usage_collector.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { KibanaConfig } from 'src/legacy/server/kbn_server'; import { get } from 'lodash'; import { CallAPIOptions } from 'src/core/server'; import { XPackMainPlugin } from '../../../xpack_main/xpack_main'; @@ -114,7 +113,7 @@ export interface UsageStats { } interface CollectorDeps { - config: KibanaConfig; + kibanaIndex: string; usage: { collectorSet: any }; xpackMain: XPackMainPlugin; } @@ -131,19 +130,17 @@ export function getSpacesUsageCollector(deps: CollectorDeps) { fetch: async (callCluster: CallCluster) => { const xpackInfo = deps.xpackMain.info; const available = xpackInfo && xpackInfo.isAvailable(); // some form of spaces is available for all valid licenses - const enabled = deps.config.get('xpack.spaces.enabled'); - const spacesAvailableAndEnabled = Boolean(available && enabled); const usageStats = await getSpacesUsage( callCluster, - deps.config.get('kibana.index'), + deps.kibanaIndex, deps.xpackMain, - spacesAvailableAndEnabled + available ); return { available, - enabled: spacesAvailableAndEnabled, // similar behavior as _xpack API in ES + enabled: available, ...usageStats, } as UsageStats; }, diff --git a/x-pack/legacy/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts b/x-pack/legacy/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts index fdc6fda26effd..677b10de8ecbf 100644 --- a/x-pack/legacy/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts +++ b/x-pack/legacy/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts @@ -3,260 +3,232 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - import * as Rx from 'rxjs'; -import { SavedObject, SavedObjectsService } from 'src/core/server'; -import { Feature } from '../../../../../../plugins/features/server'; -import { convertSavedObjectToSpace } from '../../routes/lib'; -import { initSpacesOnPostAuthRequestInterceptor } from './on_post_auth_interceptor'; +import { Legacy } from 'kibana'; +// @ts-ignore +import { kibanaTestUser } from '@kbn/test'; import { initSpacesOnRequestInterceptor } from './on_request_interceptor'; -import { SpacesService } from '../../new_platform/spaces_service'; -import { SecurityPlugin } from '../../../../security'; -import { SpacesAuditLogger } from '../audit_logger'; -import { SpacesServiceSetup } from '../../new_platform/spaces_service/spaces_service'; +import { + HttpServiceSetup, + CoreSetup, + SavedObjectsService, +} from '../../../../../../../src/core/server'; import { elasticsearchServiceMock, - httpServiceMock, - httpServerMock, + loggingServiceMock, } from '../../../../../../../src/core/server/mocks'; import * as kbnTestServer from '../../../../../../../src/test_utils/kbn_server'; -import { HttpServiceSetup } from 'src/core/server'; -import { KibanaConfig, Server } from 'src/legacy/server/kbn_server'; -import { XPackMainPlugin } from '../../../../xpack_main/xpack_main'; -import { parse } from 'url'; +import { LegacyAPI } from '../../new_platform/plugin'; +import { SpacesService } from '../../new_platform/spaces_service'; import { OptionalPlugin } from '../../../../../server/lib/optional_plugin'; +import { SpacesAuditLogger } from '../audit_logger'; +import { SecurityPlugin } from '../../../../security'; +import { convertSavedObjectToSpace } from '../../routes/lib'; +import { XPackMainPlugin } from '../../../../xpack_main/xpack_main'; +import { Feature } from '../../../../../../plugins/features/server'; +import { initSpacesOnPostAuthRequestInterceptor } from './on_post_auth_interceptor'; + +describe('onPostAuthInterceptor', () => { + let root: ReturnType; -// TODO: re-implement on NP -describe('onPostAuthRequestInterceptor', () => { + const defaultRoute = '/app/kibana'; const headers = { - authorization: 'foo', + authorization: `Basic ${Buffer.from( + `${kibanaTestUser.username}:${kibanaTestUser.password}` + ).toString('base64')}`, }; - let request: any; - let spacesService: SpacesServiceSetup; - const serverBasePath = '/'; - const defaultRoute = '/app/custom-app'; + beforeEach(async () => { + root = kbnTestServer.createRoot(); + }, 30000); - let root: ReturnType; + afterEach(async () => await root.shutdown()); - function initKbnServer(http: HttpServiceSetup) { + function initKbnServer(http: HttpServiceSetup, routes: 'legacy' | 'new-platform') { const kbnServer = kbnTestServer.getKbnServer(root); - kbnServer.server.route([ - { - method: 'GET', - path: '/foo', - handler: (req: any) => { - return { path: req.path, basePath: http.basePath.get(req) }; + if (routes === 'legacy') { + kbnServer.server.route([ + { + method: 'GET', + path: '/foo', + handler: (req: Legacy.Request, h: Legacy.ResponseToolkit) => { + return h.response({ path: req.path, basePath: http.basePath.get(req) }); + }, }, - }, - { - method: 'GET', - path: '/app/kibana', - handler: (req: any) => { - return { path: req.path, basePath: http.basePath.get(req) }; + { + method: 'GET', + path: '/app/kibana', + handler: (req: Legacy.Request, h: Legacy.ResponseToolkit) => { + return h.response({ path: req.path, basePath: http.basePath.get(req) }); + }, }, - }, - { - method: 'GET', - path: '/app/app-1', - handler: (req: any) => { - return { path: req.path, basePath: http.basePath.get(req) }; + { + method: 'GET', + path: '/app/app-1', + handler: (req: Legacy.Request, h: Legacy.ResponseToolkit) => { + return h.response({ path: req.path, basePath: http.basePath.get(req) }); + }, }, - }, - { - method: 'GET', - path: '/app/app-2', - handler: (req: any) => { - return { path: req.path, basePath: http.basePath.get(req) }; + { + method: 'GET', + path: '/app/app-2', + handler: (req: Legacy.Request, h: Legacy.ResponseToolkit) => { + return h.response({ path: req.path, basePath: http.basePath.get(req) }); + }, }, - }, - { - method: 'GET', - path: '/api/test/foo', - handler: (req: any) => { - return { path: req.path, basePath: http.basePath.get(req) }; + { + method: 'GET', + path: '/api/test/foo', + handler: (req: Legacy.Request) => { + return { path: req.path, basePath: http.basePath.get(req) }; + }, }, - }, - ]); - } + { + method: 'GET', + path: '/some/path/s/foo/bar', + handler: (req: Legacy.Request, h: Legacy.ResponseToolkit) => { + return h.response({ path: req.path, basePath: http.basePath.get(req) }); + }, + }, + ]); + } - beforeEach(() => { - root = kbnTestServer.createRoot(); - request = async ( - path: string, - spaces: SavedObject[], - setupFn: (server: Server) => null = () => null - ) => { - const { http } = await root.setup(); - - interface Config { - [key: string]: any; - } - const config: Config = { - 'server.basePath': serverBasePath, - 'server.defaultRoute': defaultRoute, - }; + if (routes === 'new-platform') { + const router = http.createRouter('/'); - const configFn = jest.fn(() => { - return { - get: jest.fn(key => { - return config[key]; - }), - }; + router.get({ path: '/api/np_test/foo', validate: false }, (context, req, h) => { + return h.ok({ body: { path: req.url.pathname, basePath: http.basePath.get(req) } }); }); + } + } - const savedObjectsService = { - SavedObjectsClient: { - errors: { - isNotFoundError: (e: Error) => e.message === 'space not found', + async function request( + path: string, + availableSpaces: any[], + testOptions = { simulateGetSpacesFailure: false } + ) { + const { http } = await root.setup(); + + const loggingMock = loggingServiceMock + .create() + .asLoggerFactory() + .get('xpack', 'spaces'); + + const xpackMainPlugin = { + getFeatures: () => + [ + { + id: 'feature-1', + name: 'feature 1', + app: ['app-1'], }, - }, - getSavedObjectsRepository: jest.fn().mockImplementation(() => { - return { - get: (type: string, id: string) => { - if (type === 'space') { - const space = spaces.find(s => s.id === id); - if (space) { - return space; - } - throw new Error('space not found'); - } - }, - create: () => null, - }; - }), - }; + { + id: 'feature-2', + name: 'feature 2', + app: ['app-2'], + }, + { + id: 'feature-4', + name: 'feature 4', + app: ['app-1', 'app-4'], + }, + { + id: 'feature-5', + name: 'feature 4', + app: ['kibana'], + }, + ] as Feature[], + } as XPackMainPlugin; - const plugins = { - elasticsearch: { - getCluster: jest.fn().mockReturnValue({ - callWithInternalUser: jest.fn(), - callWithRequest: jest.fn(), - }), + const savedObjectsService = { + SavedObjectsClient: { + errors: { + isNotFoundError: (e: Error) => e.message === 'space not found', }, - spaces: { - spacesClient: { - scopedClient: jest.fn(), + }, + getSavedObjectsRepository: jest.fn().mockImplementation(() => { + return { + get: (type: string, id: string) => { + if (type === 'space') { + const space = availableSpaces.find(s => s.id === id); + if (space) { + return space; + } + throw new Error('space not found'); + } }, - }, - xpack_main: { - getFeatures: () => - [ - { - id: 'feature-1', - name: 'feature 1', - app: ['app-1'], - }, - { - id: 'feature-2', - name: 'feature 2', - app: ['app-2'], - }, - { - id: 'feature-4', - name: 'feature 4', - app: ['app-1', 'app-4'], - }, - { - id: 'feature-5', - name: 'feature 4', - app: ['kibana'], - }, - ] as Feature[], - }, - }; - - const log = { - log: jest.fn(), - trace: jest.fn(), - debug: jest.fn(), - info: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - fatal: jest.fn(), - }; + create: () => null, + }; + }), + }; - let basePath: string | undefined; + const legacyAPI = { + legacyConfig: { + serverDefaultRoute: defaultRoute, + serverBasePath: '', + }, + savedObjects: (savedObjectsService as unknown) as SavedObjectsService, + } as LegacyAPI; - const httpMock = httpServiceMock.createSetupContract(); + const service = new SpacesService(loggingMock, () => legacyAPI); - httpMock.basePath.get = jest.fn().mockImplementation(() => basePath); - httpMock.basePath.set = jest.fn().mockImplementation((req: any, newPath: string) => { - basePath = newPath; - }); - const preAuthToolkit = httpServiceMock.createOnPreAuthToolkit(); - preAuthToolkit.rewriteUrl.mockImplementation(url => { - path = url; - return null as any; - }); - httpMock.registerOnPreAuth = jest.fn().mockImplementation(async handler => { - const preAuthRequest = { - path, - url: parse(path), - }; - await handler( - preAuthRequest, - httpServerMock.createLifecycleResponseFactory(), - preAuthToolkit - ); - }); + const spacesService = await service.setup({ + http: (http as unknown) as CoreSetup['http'], + elasticsearch: elasticsearchServiceMock.createSetupContract(), + security: {} as OptionalPlugin, + getSpacesAuditLogger: () => ({} as SpacesAuditLogger), + config$: Rx.of({ maxSpaces: 1000 }), + }); - const service = new SpacesService(log, configFn().get('server.basePath')); - spacesService = await service.setup({ - http: httpMock, - elasticsearch: elasticsearchServiceMock.createSetupContract(), - savedObjects: (savedObjectsService as unknown) as SavedObjectsService, - security: {} as OptionalPlugin, - spacesAuditLogger: {} as SpacesAuditLogger, - config$: Rx.of({ maxSpaces: 1000 }), - }); + spacesService.scopedClient = jest.fn().mockResolvedValue({ + getAll() { + if (testOptions.simulateGetSpacesFailure) { + throw new Error('unknown error retrieving all spaces'); + } + return Promise.resolve(availableSpaces.map(convertSavedObjectToSpace)); + }, + get(spaceId: string) { + const space = availableSpaces.find(s => s.id === spaceId); + if (!space) { + throw new Error('space not found'); + } + return Promise.resolve(convertSavedObjectToSpace(space)); + }, + }); - spacesService.scopedClient = jest.fn().mockResolvedValue({ - getAll() { - return spaces.map(convertSavedObjectToSpace); - }, - get(spaceId: string) { - const space = spaces.find(s => s.id === spaceId); - if (!space) { - throw new Error('space not found'); - } - return convertSavedObjectToSpace(space); - }, - }); + // The onRequest interceptor is also included here because the onPostAuth interceptor requires the onRequest + // interceptor to parse out the space id and rewrite the request's URL. Rather than duplicating that logic, + // we are including the already tested interceptor here in the test chain. + initSpacesOnRequestInterceptor({ + getLegacyAPI: () => legacyAPI, + http: (http as unknown) as CoreSetup['http'], + }); - // The onRequest interceptor is also included here because the onPostAuth interceptor requires the onRequest - // interceptor to parse out the space id and rewrite the request's URL. Rather than duplicating that logic, - // we are including the already tested interceptor here in the test chain. - initSpacesOnRequestInterceptor({ - config: (configFn() as unknown) as KibanaConfig, - http: httpMock, - }); + initSpacesOnPostAuthRequestInterceptor({ + getLegacyAPI: () => legacyAPI, + http: (http as unknown) as CoreSetup['http'], + log: loggingMock, + xpackMain: xpackMainPlugin, + spacesService, + }); - await root.start(); + initKbnServer(http, 'new-platform'); - const legacyServer = kbnTestServer.getKbnServer(root).server; + await root.start(); - initSpacesOnPostAuthRequestInterceptor({ - config: (configFn() as unknown) as KibanaConfig, - onPostAuth: (handler: any) => legacyServer.ext('onPostAuth', handler), - getHiddenUiAppById: (app: string) => null, - http: httpMock, - log, - xpackMain: plugins.xpack_main as XPackMainPlugin, - spacesService, - }); + initKbnServer(http, 'legacy'); - initKbnServer(http); + const response = await kbnTestServer.request.get(root, path); - return await kbnTestServer.request.get(root, path); + return { + response, + spacesService, }; - }, 30000); - - afterEach(async () => await root.shutdown()); + } - describe('when accessing an app within a non-existent space', () => { - it('redirects to the space selector screen', async () => { + describe('requests proxied to the legacy platform', () => { + it('redirects to the space selector screen when accessing an app within a non-existent space', async () => { const spaces = [ { id: 'a-space', @@ -267,32 +239,30 @@ describe('onPostAuthRequestInterceptor', () => { }, ]; - const response = await request('/s/not-found/app/kibana', spaces); + const { response } = await request('/s/not-found/app/kibana', spaces); - expect(response.statusCode).toEqual(302); - expect(response.headers.location).toEqual(serverBasePath); + expect(response.status).toEqual(302); + expect(response.header.location).toEqual(`/spaces/space_selector`); }, 30000); - }); - it('when accessing the kibana app it always allows the request to continue', async () => { - const spaces = [ - { - id: 'a-space', - type: 'space', - attributes: { - name: 'a space', - disabledFeatures: ['feature-1', 'feature-2', 'feature-4', 'feature-5'], + it('when accessing the kibana app it always allows the request to continue', async () => { + const spaces = [ + { + id: 'a-space', + type: 'space', + attributes: { + name: 'a space', + disabledFeatures: ['feature-1', 'feature-2', 'feature-4', 'feature-5'], + }, }, - }, - ]; + ]; - const response = await request('/s/a-space/app/kibana', spaces); + const { response } = await request('/s/a-space/app/kibana', spaces); - expect(response.statusCode).toEqual(200); - }, 30000); + expect(response.status).toEqual(200); + }, 30000); - describe('when accessing an API endpoint within a non-existent space', () => { - it('allows the request to continue', async () => { + it('allows the request to continue when accessing an API endpoint within a non-existent space', async () => { const spaces = [ { id: 'a-space', @@ -303,14 +273,14 @@ describe('onPostAuthRequestInterceptor', () => { }, ]; - const response = await request('/s/not-found/api/test/foo', spaces); + const { response } = await request('/s/not-found/api/test/foo', spaces); - expect(response.statusCode).toEqual(200); + expect(response.status).toEqual(200); }, 30000); }); - describe.skip('with a single available space', () => { - test('it redirects to the defaultRoute within the context of the single Space when navigating to Kibana root', async () => { + describe('requests handled completely in the new platform', () => { + it('redirects to the space selector screen when accessing an app within a non-existent space', async () => { const spaces = [ { id: 'a-space', @@ -321,39 +291,146 @@ describe('onPostAuthRequestInterceptor', () => { }, ]; - const response = await request('/', spaces); + const { response } = await request('/s/not-found/app/np_kibana', spaces); - expect(response.statusCode).toEqual(302); - expect(response.headers.location).toEqual(`${serverBasePath}/s/a-space${defaultRoute}`); + expect(response.status).toEqual(302); + expect(response.header.location).toEqual(`/spaces/space_selector`); + }, 30000); - expect(spacesService.scopedClient).toHaveBeenCalledWith( - expect.objectContaining({ - headers: expect.objectContaining({ - authorization: headers.authorization, - }), - }) - ); + it('allows the request to continue when accessing an API endpoint within a non-existent space', async () => { + const spaces = [ + { + id: 'a-space', + type: 'space', + attributes: { + name: 'a space', + }, + }, + ]; + + const { response } = await request('/s/not-found/api/np_test/foo', spaces); + + expect(response.status).toEqual(200); + }, 30000); + }); + + it('handles space retrieval errors gracefully', async () => { + const spaces = [ + { + id: 'a-space', + type: 'space', + attributes: { + name: 'a space', + }, + }, + ]; + + const { response, spacesService } = await request('/', spaces, { + simulateGetSpacesFailure: true, }); - test('it redirects to the defaultRoute within the context of the Default Space when navigating to Kibana root', async () => { - // This is very similar to the test above, but this handles the condition where the only available space is the Default Space, - // which does not have a URL Context. In this scenario, the end result is the same as the other test, but the final URL the user - // is redirected to does not contain a space identifier (e.g., /s/foo) + expect(response.status).toEqual(500); + expect(response.body).toMatchInlineSnapshot(` + Object { + "error": "Internal Server Error", + "message": "unknown error retrieving all spaces", + "statusCode": 500, + } + `); + + expect(spacesService.scopedClient).toHaveBeenCalledWith( + expect.objectContaining({ + headers: expect.objectContaining({ + authorization: headers.authorization, + }), + }) + ); + }); + + it('redirects to the space selector when accessing the root of the default space', async () => { + const spaces = [ + { + id: 'default', + type: 'space', + attributes: { + name: 'Default space', + _reserved: true, + }, + }, + { + id: 'a-space', + type: 'space', + attributes: { + name: 'a space', + }, + }, + ]; + + const { response, spacesService } = await request('/', spaces); + + expect(response.status).toEqual(302); + expect(response.header.location).toEqual(`/spaces/space_selector`); + + expect(spacesService.scopedClient).toHaveBeenCalledWith( + expect.objectContaining({ + headers: expect.objectContaining({ + authorization: headers.authorization, + }), + }) + ); + }, 30000); + + it('allows the request to continue when accessing the root of a non-default space', async () => { + const spaces = [ + { + id: 'default', + type: 'space', + attributes: { + name: 'Default space', + _reserved: true, + }, + }, + { + id: 'a-space', + type: 'space', + attributes: { + name: 'a space', + }, + }, + ]; + + const { response, spacesService } = await request('/s/a-space', spaces); + + // OSS handles this redirection for us + expect(response.status).toEqual(302); + expect(response.header.location).toEqual(`/s/a-space${defaultRoute}`); + expect(spacesService.scopedClient).toHaveBeenCalledWith( + expect.objectContaining({ + headers: expect.objectContaining({ + authorization: headers.authorization, + }), + }) + ); + }, 30000); + + describe('with a single available space', () => { + it('it redirects to the defaultRoute within the context of the single Space when navigating to Kibana root', async () => { const spaces = [ { - id: 'default', + id: 'a-space', type: 'space', attributes: { - name: 'Default Space', + name: 'a space', }, }, ]; - const response = await request('/', spaces); + const { response, spacesService } = await request('/', spaces); + + expect(response.status).toEqual(302); + expect(response.header.location).toEqual(`/s/a-space${defaultRoute}`); - expect(response.statusCode).toEqual(302); - expect(response.headers.location).toEqual(`${serverBasePath}${defaultRoute}`); expect(spacesService.scopedClient).toHaveBeenCalledWith( expect.objectContaining({ headers: expect.objectContaining({ @@ -363,22 +440,25 @@ describe('onPostAuthRequestInterceptor', () => { ); }); - test('it allows navigation to apps when none are disabled', async () => { + it('it redirects to the defaultRoute within the context of the Default Space when navigating to Kibana root', async () => { + // This is very similar to the test above, but this handles the condition where the only available space is the Default Space, + // which does not have a URL Context. In this scenario, the end result is the same as the other test, but the final URL the user + // is redirected to does not contain a space identifier (e.g., /s/foo) + const spaces = [ { - id: 'a-space', + id: 'default', type: 'space', attributes: { - name: 'a space', - disabledFeatures: [] as any, + name: 'Default Space', }, }, ]; - const response = await request('/s/a-space/app/kibana', spaces); - - expect(response.statusCode).toEqual(200); + const { response, spacesService } = await request('/', spaces); + expect(response.status).toEqual(302); + expect(response.header.location).toEqual(defaultRoute); expect(spacesService.scopedClient).toHaveBeenCalledWith( expect.objectContaining({ headers: expect.objectContaining({ @@ -388,21 +468,21 @@ describe('onPostAuthRequestInterceptor', () => { ); }); - test('allows navigation to app that is granted by multiple features, when only one of those features is disabled', async () => { + it('it allows navigation to apps when none are disabled', async () => { const spaces = [ { id: 'a-space', type: 'space', attributes: { name: 'a space', - disabledFeatures: ['feature-1'] as any, + disabledFeatures: [], }, }, ]; - const response = await request('/s/a-space/app/app-1', spaces); + const { response, spacesService } = await request('/s/a-space/app/kibana', spaces); - expect(response.statusCode).toEqual(200); + expect(response.status).toEqual(200); expect(spacesService.scopedClient).toHaveBeenCalledWith( expect.objectContaining({ @@ -413,21 +493,21 @@ describe('onPostAuthRequestInterceptor', () => { ); }); - test('does not allow navigation to apps that are only provided by a disabled feature', async () => { + it('allows navigation to app that is granted by multiple features, when only one of those features is disabled', async () => { const spaces = [ { id: 'a-space', type: 'space', attributes: { name: 'a space', - disabledFeatures: ['feature-2'] as any, + disabledFeatures: ['feature-1'], }, }, ]; - const response = await request('/s/a-space/app/app-2', spaces); + const { response, spacesService } = await request('/s/a-space/app/app-1', spaces); - expect(response.statusCode).toEqual(404); + expect(response.status).toEqual(200); expect(spacesService.scopedClient).toHaveBeenCalledWith( expect.objectContaining({ @@ -437,36 +517,23 @@ describe('onPostAuthRequestInterceptor', () => { }) ); }); - }); - describe.skip('with multiple available spaces', () => { - test('it redirects to the Space Selector App when navigating to Kibana root', async () => { + it('does not allow navigation to apps that are only provided by a disabled feature', async () => { const spaces = [ { id: 'a-space', type: 'space', attributes: { name: 'a space', - }, - }, - { - id: 'b-space', - type: 'space', - attributes: { - name: 'b space', + disabledFeatures: ['feature-2'] as any, }, }, ]; - const getHiddenUiAppHandler = jest.fn(() => '
space selector
'); - - const response = await request('/', spaces); + const { response, spacesService } = await request('/s/a-space/app/app-2', spaces); - expect(response.statusCode).toEqual(200); - expect(response.payload).toEqual('
space selector
'); + expect(response.status).toEqual(404); - expect(getHiddenUiAppHandler).toHaveBeenCalledTimes(1); - expect(getHiddenUiAppHandler).toHaveBeenCalledWith('space_selector'); expect(spacesService.scopedClient).toHaveBeenCalledWith( expect.objectContaining({ headers: expect.objectContaining({ diff --git a/x-pack/legacy/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.ts b/x-pack/legacy/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.ts index dc3f645b4cd5a..0fed454621e04 100644 --- a/x-pack/legacy/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.ts +++ b/x-pack/legacy/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.ts @@ -3,41 +3,41 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; -import { KibanaConfig } from 'src/legacy/server/kbn_server'; -import { HttpServiceSetup, Logger } from 'src/core/server'; +import { Logger, CoreSetup } from 'src/core/server'; import { Space } from '../../../common/model/space'; import { wrapError } from '../errors'; -import { getSpaceSelectorUrl } from '../get_space_selector_url'; -import { addSpaceIdToPath, getSpaceIdFromPath } from '../spaces_url_parser'; +import { addSpaceIdToPath } from '../spaces_url_parser'; import { XPackMainPlugin } from '../../../../xpack_main/xpack_main'; import { SpacesServiceSetup } from '../../new_platform/spaces_service/spaces_service'; +import { LegacyAPI } from '../../new_platform/plugin'; +import { getSpaceSelectorUrl } from '../get_space_selector_url'; +import { DEFAULT_SPACE_ID } from '../../../common/constants'; export interface OnPostAuthInterceptorDeps { - config: KibanaConfig; - onPostAuth: (handler: any) => void; - getHiddenUiAppById: (appId: string) => unknown; - http: HttpServiceSetup; + getLegacyAPI(): LegacyAPI; + http: CoreSetup['http']; xpackMain: XPackMainPlugin; spacesService: SpacesServiceSetup; log: Logger; } export function initSpacesOnPostAuthRequestInterceptor({ - config, xpackMain, + getLegacyAPI, spacesService, log, http, - onPostAuth, - getHiddenUiAppById, }: OnPostAuthInterceptorDeps) { - const serverBasePath: string = config.get('server.basePath'); + const { serverBasePath, serverDefaultRoute } = getLegacyAPI().legacyConfig; + + http.registerOnPostAuth(async (request, response, toolkit) => { + const path = request.url.pathname!; - onPostAuth(async function spacesOnPostAuthHandler(request: any, h: any) { - const path = request.path; + const spaceId = spacesService.getSpaceId(request); - const isRequestingKibanaRoot = path === '/'; + // The root of kibana is also the root of the defaut space, + // since the default space does not have a URL Identifier (i.e., `/s/foo`). + const isRequestingKibanaRoot = path === '/' && spaceId === DEFAULT_SPACE_ID; const isRequestingApplication = path.startsWith('/app'); const spacesClient = await spacesService.scopedClient(request); @@ -49,36 +49,35 @@ export function initSpacesOnPostAuthRequestInterceptor({ try { const spaces = await spacesClient.getAll(); - const basePath: string = config.get('server.basePath'); - const defaultRoute: string = config.get('server.defaultRoute'); - if (spaces.length === 1) { // If only one space is available, then send user there directly. // No need for an interstitial screen where there is only one possible outcome. const space = spaces[0]; - const destination = addSpaceIdToPath(basePath, space.id, defaultRoute); - return h.redirect(destination).takeover(); + const destination = addSpaceIdToPath(serverBasePath, space.id, serverDefaultRoute); + return response.redirected({ headers: { location: destination } }); } if (spaces.length > 0) { // render spaces selector instead of home page - const app = getHiddenUiAppById('space_selector'); - return (await h.renderApp(app, { spaces })).takeover(); + return response.redirected({ + headers: { location: getSpaceSelectorUrl(serverBasePath) }, + }); } } catch (error) { - return wrapError(error); + const wrappedError = wrapError(error); + return response.customError({ + body: wrappedError, + statusCode: wrappedError.output.statusCode, + }); } } // This condition should only happen after selecting a space, or when transitioning from one application to another // e.g.: Navigating from Dashboard to Timelion if (isRequestingApplication) { - let spaceId: string = ''; let space: Space; try { - spaceId = getSpaceIdFromPath(http.basePath.get(request), serverBasePath); - log.debug(`Verifying access to space "${spaceId}"`); space = await spacesClient.get(spaceId); @@ -87,7 +86,10 @@ export function initSpacesOnPostAuthRequestInterceptor({ `Unable to navigate to space "${spaceId}", redirecting to Space Selector. ${error}` ); // Space doesn't exist, or user not authorized for space, or some other issue retrieving the active space. - return h.redirect(getSpaceSelectorUrl(config)).takeover(); + const result = response.redirected({ + headers: { location: getSpaceSelectorUrl(serverBasePath) }, + }); + return result; } // Verify application is available in this space @@ -105,13 +107,14 @@ export function initSpacesOnPostAuthRequestInterceptor({ ); const isAvailableInSpace = enabledFeatures.some(feature => feature.app.includes(appId)); + if (!isAvailableInSpace) { log.error(`App ${appId} is not enabled within space "${spaceId}".`); - return Boom.notFound(); + return response.notFound(); } } } } - return h.continue; + return toolkit.next(); }); } diff --git a/x-pack/legacy/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.test.ts b/x-pack/legacy/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.test.ts index a0afede2d12dd..e8bfab9fb1df0 100644 --- a/x-pack/legacy/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.test.ts +++ b/x-pack/legacy/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.test.ts @@ -11,10 +11,11 @@ import { HttpServiceSetup, KibanaRequest, KibanaResponseFactory, + CoreSetup, } from '../../../../../../../src/core/server'; import * as kbnTestServer from '../../../../../../../src/test_utils/kbn_server'; -import { KibanaConfig } from '../../../../../../../src/legacy/server/kbn_server'; +import { LegacyAPI } from '../../new_platform/plugin'; describe('onRequestInterceptor', () => { let root: ReturnType; @@ -62,14 +63,14 @@ describe('onRequestInterceptor', () => { const router = http.createRouter('/'); router.get( - { path: '/foo', validate: false }, + { path: '/np_foo', validate: false }, (context: unknown, req: KibanaRequest, h: KibanaResponseFactory) => { return h.ok({ body: { path: req.url.pathname, basePath: http.basePath.get(req) } }); } ); router.get( - { path: '/some/path/s/foo/bar', validate: false }, + { path: '/some/path/s/np_foo/bar', validate: false }, (context: unknown, req: KibanaRequest, h: KibanaResponseFactory) => { return h.ok({ body: { path: req.url.pathname, basePath: http.basePath.get(req) } }); } @@ -77,7 +78,7 @@ describe('onRequestInterceptor', () => { router.get( { - path: '/i/love/spaces', + path: '/i/love/np_spaces', validate: { query: schema.object({ queryParam: schema.string({ @@ -99,20 +100,37 @@ describe('onRequestInterceptor', () => { } } - describe('requests proxied to the legacy platform', () => { - it('handles paths without a space identifier', async () => { - const { http } = await root.setup(); + interface SetupOpts { + basePath: string; + routes: 'legacy' | 'new-platform'; + } + async function setup(opts: SetupOpts = { basePath: '/', routes: 'legacy' }) { + const { http } = await root.setup(); + + initSpacesOnRequestInterceptor({ + getLegacyAPI: () => + ({ + legacyConfig: { + serverBasePath: opts.basePath, + }, + } as LegacyAPI), + http: (http as unknown) as CoreSetup['http'], + }); - const basePath = '/'; - const config = ({ - get: jest.fn().mockReturnValue(basePath), - } as unknown) as KibanaConfig; + initKbnServer(http, 'new-platform'); - initSpacesOnRequestInterceptor({ config, http }); + await root.start(); - await root.start(); + initKbnServer(http, 'legacy'); - initKbnServer(http, 'legacy'); + return { + http, + }; + } + + describe('requests proxied to the legacy platform', () => { + it('handles paths without a space identifier', async () => { + await setup(); const path = '/foo'; @@ -123,18 +141,7 @@ describe('onRequestInterceptor', () => { }, 30000); it('strips the Space URL Context from the request', async () => { - const { http } = await root.setup(); - - const basePath = '/'; - const config = ({ - get: jest.fn().mockReturnValue(basePath), - } as unknown) as KibanaConfig; - - initSpacesOnRequestInterceptor({ config, http }); - - await root.start(); - - initKbnServer(http, 'legacy'); + await setup(); const path = '/s/foo-space/foo'; @@ -148,18 +155,7 @@ describe('onRequestInterceptor', () => { }, 30000); it('ignores space identifiers in the middle of the path', async () => { - const { http } = await root.setup(); - - const basePath = '/'; - const config = ({ - get: jest.fn().mockReturnValue(basePath), - } as unknown) as KibanaConfig; - - initSpacesOnRequestInterceptor({ config, http }); - - await root.start(); - - initKbnServer(http, 'legacy'); + await setup(); const path = '/some/path/s/foo/bar'; @@ -170,18 +166,7 @@ describe('onRequestInterceptor', () => { }, 30000); it('strips the Space URL Context from the request, maintaining the rest of the path', async () => { - const { http } = await root.setup(); - - const basePath = '/'; - const config = ({ - get: jest.fn().mockReturnValue(basePath), - } as unknown) as KibanaConfig; - - initSpacesOnRequestInterceptor({ config, http }); - - await root.start(); - - initKbnServer(http, 'legacy'); + await setup(); const path = '/s/foo/i/love/spaces?queryParam=queryValue'; @@ -197,20 +182,9 @@ describe('onRequestInterceptor', () => { describe('requests handled completely in the new platform', () => { it('handles paths without a space identifier', async () => { - const { http } = await root.setup(); - - initKbnServer(http, 'new-platform'); + await setup({ basePath: '/', routes: 'new-platform' }); - const basePath = '/'; - const config = ({ - get: jest.fn().mockReturnValue(basePath), - } as unknown) as KibanaConfig; - - initSpacesOnRequestInterceptor({ config, http }); - - await root.start(); - - const path = '/foo'; + const path = '/np_foo'; await kbnTestServer.request.get(root, path).expect(200, { path, @@ -219,67 +193,34 @@ describe('onRequestInterceptor', () => { }, 30000); it('strips the Space URL Context from the request', async () => { - const { http } = await root.setup(); - - initKbnServer(http, 'new-platform'); - - const basePath = '/'; - const config = ({ - get: jest.fn().mockReturnValue(basePath), - } as unknown) as KibanaConfig; + await setup({ basePath: '/', routes: 'new-platform' }); - initSpacesOnRequestInterceptor({ config, http }); - - await root.start(); - - const path = '/s/foo-space/foo'; + const path = '/s/foo-space/np_foo'; await kbnTestServer.request.get(root, path).expect(200, { - path: '/foo', + path: '/np_foo', basePath: '/s/foo-space', }); }, 30000); it('ignores space identifiers in the middle of the path', async () => { - const { http } = await root.setup(); - - initKbnServer(http, 'new-platform'); + await setup({ basePath: '/', routes: 'new-platform' }); - const basePath = '/'; - const config = ({ - get: jest.fn().mockReturnValue(basePath), - } as unknown) as KibanaConfig; - - initSpacesOnRequestInterceptor({ config, http }); - - await root.start(); - - const path = '/some/path/s/foo/bar'; + const path = '/some/path/s/np_foo/bar'; await kbnTestServer.request.get(root, path).expect(200, { - path: '/some/path/s/foo/bar', + path: '/some/path/s/np_foo/bar', basePath: '', // no base path set for route within the default space }); }, 30000); it('strips the Space URL Context from the request, maintaining the rest of the path', async () => { - const { http } = await root.setup(); - - initKbnServer(http, 'new-platform'); + await setup({ basePath: '/', routes: 'new-platform' }); - const basePath = '/'; - const config = ({ - get: jest.fn().mockReturnValue(basePath), - } as unknown) as KibanaConfig; - - initSpacesOnRequestInterceptor({ config, http }); - - await root.start(); - - const path = '/s/foo/i/love/spaces?queryParam=queryValue'; + const path = '/s/foo/i/love/np_spaces?queryParam=queryValue'; await kbnTestServer.request.get(root, path).expect(200, { - path: '/i/love/spaces', + path: '/i/love/np_spaces', basePath: '/s/foo', query: { queryParam: 'queryValue', diff --git a/x-pack/legacy/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.ts b/x-pack/legacy/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.ts index 88b6b5cd899ca..5da9bdbe6543f 100644 --- a/x-pack/legacy/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.ts +++ b/x-pack/legacy/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.ts @@ -7,26 +7,25 @@ import { KibanaRequest, OnPreAuthToolkit, LifecycleResponseFactory, - HttpServiceSetup, + CoreSetup, } from 'src/core/server'; -import { KibanaConfig } from 'src/legacy/server/kbn_server'; import { format } from 'url'; import { DEFAULT_SPACE_ID } from '../../../common/constants'; import { getSpaceIdFromPath } from '../spaces_url_parser'; import { modifyUrl } from '../utils/url'; +import { LegacyAPI } from '../../new_platform/plugin'; export interface OnRequestInterceptorDeps { - config: KibanaConfig; - http: HttpServiceSetup; + getLegacyAPI(): LegacyAPI; + http: CoreSetup['http']; } -export function initSpacesOnRequestInterceptor({ config, http }: OnRequestInterceptorDeps) { - const serverBasePath: string = config.get('server.basePath'); - +export function initSpacesOnRequestInterceptor({ getLegacyAPI, http }: OnRequestInterceptorDeps) { http.registerOnPreAuth(async function spacesOnPreAuthHandler( request: KibanaRequest, response: LifecycleResponseFactory, toolkit: OnPreAuthToolkit ) { + const { serverBasePath } = getLegacyAPI().legacyConfig; const path = request.url.pathname; // If navigating within the context of a space, then we store the Space's URL Context on the request, diff --git a/x-pack/legacy/plugins/spaces/server/lib/spaces_tutorial_context_factory.test.ts b/x-pack/legacy/plugins/spaces/server/lib/spaces_tutorial_context_factory.test.ts index 4c548924c2818..eed72ec744e0f 100644 --- a/x-pack/legacy/plugins/spaces/server/lib/spaces_tutorial_context_factory.test.ts +++ b/x-pack/legacy/plugins/spaces/server/lib/spaces_tutorial_context_factory.test.ts @@ -10,22 +10,10 @@ import { createSpacesTutorialContextFactory } from './spaces_tutorial_context_fa import { SpacesService } from '../new_platform/spaces_service'; import { SavedObjectsService } from 'src/core/server'; import { SpacesAuditLogger } from './audit_logger'; -import { elasticsearchServiceMock, httpServiceMock } from '../../../../../../src/core/server/mocks'; +import { elasticsearchServiceMock, coreMock } from '../../../../../../src/core/server/mocks'; import { spacesServiceMock } from '../new_platform/spaces_service/spaces_service.mock'; import { createOptionalPlugin } from '../../../../server/lib/optional_plugin'; - -const server = { - config: () => { - return { - get: (key: string) => { - if (key === 'server.basePath') { - return '/foo'; - } - throw new Error('unexpected key ' + key); - }, - }; - }, -}; +import { LegacyAPI } from '../new_platform/plugin'; const log = { log: jest.fn(), @@ -37,7 +25,14 @@ const log = { fatal: jest.fn(), }; -const service = new SpacesService(log, server.config().get('server.basePath')); +const legacyAPI: LegacyAPI = { + legacyConfig: { + serverBasePath: '/foo', + }, + savedObjects: {} as SavedObjectsService, +} as LegacyAPI; + +const service = new SpacesService(log, () => legacyAPI); describe('createSpacesTutorialContextFactory', () => { it('should create a valid context factory', async () => { @@ -59,11 +54,10 @@ describe('createSpacesTutorialContextFactory', () => { it('should create context with the current space id for the default space', async () => { const spacesService = await service.setup({ - http: httpServiceMock.createSetupContract(), + http: coreMock.createSetup().http, elasticsearch: elasticsearchServiceMock.createSetupContract(), - savedObjects: {} as SavedObjectsService, security: createOptionalPlugin({ get: () => null }, 'xpack.security', {}, 'security'), - spacesAuditLogger: {} as SpacesAuditLogger, + getSpacesAuditLogger: () => ({} as SpacesAuditLogger), config$: Rx.of({ maxSpaces: 1000 }), }); const contextFactory = createSpacesTutorialContextFactory(spacesService); diff --git a/x-pack/legacy/plugins/spaces/server/new_platform/index.ts b/x-pack/legacy/plugins/spaces/server/new_platform/index.ts index cc0b8911b3791..edf27e2dd819b 100644 --- a/x-pack/legacy/plugins/spaces/server/new_platform/index.ts +++ b/x-pack/legacy/plugins/spaces/server/new_platform/index.ts @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Plugin, SpacesInitializerContext } from './plugin'; +import { PluginInitializerContext } from 'src/core/server'; +import { Plugin } from './plugin'; -export function plugin(initializerContext: SpacesInitializerContext) { +export function plugin(initializerContext: PluginInitializerContext) { return new Plugin(initializerContext); } diff --git a/x-pack/legacy/plugins/spaces/server/new_platform/plugin.ts b/x-pack/legacy/plugins/spaces/server/new_platform/plugin.ts index fe1f5f73e4486..382227a4f1cec 100644 --- a/x-pack/legacy/plugins/spaces/server/new_platform/plugin.ts +++ b/x-pack/legacy/plugins/spaces/server/new_platform/plugin.ts @@ -4,17 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ServerRoute } from 'hapi'; import { Observable } from 'rxjs'; -import { KibanaConfig } from 'src/legacy/server/kbn_server'; -import { SavedObjectsService } from 'src/core/server'; -import { - Logger, - HttpServiceSetup, - PluginInitializerContext, - ElasticsearchServiceSetup, -} from 'src/core/server'; +import { SavedObjectsService, CoreSetup } from 'src/core/server'; +import { Logger, PluginInitializerContext } from 'src/core/server'; import { CapabilitiesModifier } from 'src/legacy/server/capabilities'; +import { Legacy } from 'kibana'; import { OptionalPlugin } from '../../../../server/lib/optional_plugin'; import { XPackMainPlugin } from '../../../xpack_main/xpack_main'; import { createDefaultSpace } from '../lib/create_default_space'; @@ -35,14 +29,14 @@ import { SpacesServiceSetup } from './spaces_service/spaces_service'; import { SpacesConfigType } from './config'; import { getActiveSpace } from '../lib/get_active_space'; import { toggleUICapabilities } from '../lib/toggle_ui_capabilities'; +import { initSpacesRequestInterceptors } from '../lib/request_interceptors'; -export interface SpacesHttpServiceSetup extends HttpServiceSetup { - route(route: ServerRoute | ServerRoute[]): void; -} -export interface SpacesCoreSetup { - http: SpacesHttpServiceSetup; +/** + * Describes a set of APIs that is available in the legacy platform only and required by this plugin + * to function properly. + */ +export interface LegacyAPI { savedObjects: SavedObjectsService; - elasticsearch: ElasticsearchServiceSetup; usage: { collectorSet: { register: (collector: any) => void; @@ -57,6 +51,12 @@ export interface SpacesCoreSetup { auditLogger: { create: (pluginId: string) => AuditLogger; }; + legacyConfig: { + kibanaIndex: string; + serverBasePath: string; + serverDefaultRoute: string; + }; + router: Legacy.Server['route']; } export interface PluginsSetup { @@ -70,31 +70,45 @@ export interface PluginsSetup { export interface SpacesPluginSetup { spacesService: SpacesServiceSetup; - // TODO: this is temporary, required by request interceptors which are initialized in legacy plugin - log: Logger; + registerLegacyAPI: (legacyAPI: LegacyAPI) => void; } -export interface SpacesInitializerContext extends PluginInitializerContext { - legacyConfig: KibanaConfig; -} export class Plugin { private readonly pluginId = 'spaces'; - private config$: Observable; + private readonly config$: Observable; - private log: Logger; + private readonly log: Logger; - constructor(private readonly initializerContext: SpacesInitializerContext) { + private legacyAPI?: LegacyAPI; + private readonly getLegacyAPI = () => { + if (!this.legacyAPI) { + throw new Error('Legacy API is not registered!'); + } + return this.legacyAPI; + }; + + private spacesAuditLogger?: SpacesAuditLogger; + private readonly getSpacesAuditLogger = () => { + if (!this.spacesAuditLogger) { + this.spacesAuditLogger = new SpacesAuditLogger( + this.getLegacyAPI().auditLogger.create(this.pluginId) + ); + } + return this.spacesAuditLogger; + }; + + constructor(initializerContext: PluginInitializerContext) { this.config$ = initializerContext.config.create(); this.log = initializerContext.logger.get(); } - public async setup(core: SpacesCoreSetup, plugins: PluginsSetup): Promise { + public async setup(core: CoreSetup, plugins: PluginsSetup): Promise { const xpackMainPlugin: XPackMainPlugin = plugins.xpackMain; watchStatusAndLicenseToInitialize(xpackMainPlugin, plugins.spaces, async () => { await createDefaultSpace({ elasticsearch: core.elasticsearch, - savedObjects: core.savedObjects, + savedObjects: this.getLegacyAPI().savedObjects, }); }); @@ -102,76 +116,89 @@ export class Plugin { // to re-compute the license check results for this plugin. xpackMainPlugin.info.feature(this.pluginId).registerLicenseCheckResultsGenerator(checkLicense); - const spacesAuditLogger = new SpacesAuditLogger(core.auditLogger.create(this.pluginId)); + const service = new SpacesService(this.log, this.getLegacyAPI); - const service = new SpacesService( - this.log, - this.initializerContext.legacyConfig.get('server.basePath') - ); const spacesService = await service.setup({ http: core.http, elasticsearch: core.elasticsearch, - savedObjects: core.savedObjects, security: plugins.security, - spacesAuditLogger, + getSpacesAuditLogger: this.getSpacesAuditLogger, config$: this.config$, }); - const { addScopedSavedObjectsClientWrapperFactory, types } = core.savedObjects; + return { + spacesService, + registerLegacyAPI: (legacyAPI: LegacyAPI) => { + this.legacyAPI = legacyAPI; + this.setupLegacyComponents(core, spacesService, plugins.xpackMain); + }, + }; + } + + private setupLegacyComponents( + core: CoreSetup, + spacesService: SpacesServiceSetup, + xpackMainPlugin: XPackMainPlugin + ) { + const legacyAPI = this.getLegacyAPI(); + + const { addScopedSavedObjectsClientWrapperFactory, types } = legacyAPI.savedObjects; addScopedSavedObjectsClientWrapperFactory( Number.MIN_SAFE_INTEGER, 'spaces', spacesSavedObjectsClientWrapperFactory(spacesService, types) ); - core.tutorial.addScopedTutorialContextFactory( + legacyAPI.tutorial.addScopedTutorialContextFactory( createSpacesTutorialContextFactory(spacesService) ); - core.capabilities.registerCapabilitiesModifier(async (request, uiCapabilities) => { + legacyAPI.capabilities.registerCapabilitiesModifier(async (request, uiCapabilities) => { const spacesClient = await spacesService.scopedClient(request); try { const activeSpace = await getActiveSpace( spacesClient, core.http.basePath.get(request), - this.initializerContext.legacyConfig.get('server.basePath') + legacyAPI.legacyConfig.serverBasePath ); - const features = plugins.xpackMain.getFeatures(); + const features = xpackMainPlugin.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({ + kibanaIndex: legacyAPI.legacyConfig.kibanaIndex, + usage: legacyAPI.usage, + xpackMain: xpackMainPlugin, + }) + ); + initInternalApis({ - http: core.http, - config: this.initializerContext.legacyConfig, - savedObjects: core.savedObjects, + legacyRouter: legacyAPI.router, + getLegacyAPI: this.getLegacyAPI, spacesService, xpackMain: xpackMainPlugin, }); initExternalSpacesApi({ - http: core.http, + legacyRouter: legacyAPI.router, log: this.log, - savedObjects: core.savedObjects, + savedObjects: legacyAPI.savedObjects, spacesService, xpackMain: xpackMainPlugin, }); - // Register a function with server to manage the collection of usage stats - core.usage.collectorSet.register( - getSpacesUsageCollector({ - config: this.initializerContext.legacyConfig, - usage: core.usage, - xpackMain: xpackMainPlugin, - }) - ); - - return { - spacesService, + initSpacesRequestInterceptors({ + http: core.http, log: this.log, - }; + getLegacyAPI: this.getLegacyAPI, + spacesService, + xpackMain: xpackMainPlugin, + }); } } diff --git a/x-pack/legacy/plugins/spaces/server/new_platform/spaces_service/spaces_service.test.ts b/x-pack/legacy/plugins/spaces/server/new_platform/spaces_service/spaces_service.test.ts index bf2e0617dc3b6..9b8bf4826fdd0 100644 --- a/x-pack/legacy/plugins/spaces/server/new_platform/spaces_service/spaces_service.test.ts +++ b/x-pack/legacy/plugins/spaces/server/new_platform/spaces_service/spaces_service.test.ts @@ -5,12 +5,13 @@ */ import * as Rx from 'rxjs'; import { SpacesService } from './spaces_service'; -import { httpServiceMock, elasticsearchServiceMock } from 'src/core/server/mocks'; +import { coreMock, elasticsearchServiceMock } from 'src/core/server/mocks'; import { SpacesAuditLogger } from '../../lib/audit_logger'; import { KibanaRequest, SavedObjectsService } from 'src/core/server'; import { DEFAULT_SPACE_ID } from '../../../common/constants'; import { getSpaceIdFromPath } from '../../lib/spaces_url_parser'; import { createOptionalPlugin } from '../../../../../server/lib/optional_plugin'; +import { LegacyAPI } from '../plugin'; const mockLogger = { trace: jest.fn(), @@ -23,9 +24,18 @@ const mockLogger = { }; const createService = async (serverBasePath: string = '') => { - const spacesService = new SpacesService(mockLogger, serverBasePath); + const legacyAPI = { + legacyConfig: { + serverBasePath, + }, + savedObjects: ({ + getSavedObjectsRepository: jest.fn().mockReturnValue(null), + } as unknown) as SavedObjectsService, + } as LegacyAPI; + + const spacesService = new SpacesService(mockLogger, () => legacyAPI); - const httpSetup = httpServiceMock.createSetupContract(); + const httpSetup = coreMock.createSetup().http; httpSetup.basePath.get = jest.fn().mockImplementation((request: KibanaRequest) => { const spaceId = getSpaceIdFromPath(request.url.path); @@ -40,10 +50,7 @@ const createService = async (serverBasePath: string = '') => { elasticsearch: elasticsearchServiceMock.createSetupContract(), config$: Rx.of({ maxSpaces: 10 }), security: createOptionalPlugin({ get: () => null }, 'xpack.security', {}, 'security'), - savedObjects: ({ - getSavedObjectsRepository: jest.fn().mockReturnValue(null), - } as unknown) as SavedObjectsService, - spacesAuditLogger: new SpacesAuditLogger({}), + getSpacesAuditLogger: () => new SpacesAuditLogger({}), }); return spacesServiceSetup; diff --git a/x-pack/legacy/plugins/spaces/server/new_platform/spaces_service/spaces_service.ts b/x-pack/legacy/plugins/spaces/server/new_platform/spaces_service/spaces_service.ts index 74fff82f5cc70..09231b46980de 100644 --- a/x-pack/legacy/plugins/spaces/server/new_platform/spaces_service/spaces_service.ts +++ b/x-pack/legacy/plugins/spaces/server/new_platform/spaces_service/spaces_service.ts @@ -7,13 +7,7 @@ import { map, take } from 'rxjs/operators'; import { Observable, Subscription, combineLatest } from 'rxjs'; import { Legacy } from 'kibana'; -import { - Logger, - ElasticsearchServiceSetup, - HttpServiceSetup, - KibanaRequest, - SavedObjectsService, -} from 'src/core/server'; +import { Logger, KibanaRequest, CoreSetup } from 'src/core/server'; import { OptionalPlugin } from '../../../../../server/lib/optional_plugin'; import { DEFAULT_SPACE_ID } from '../../../common/constants'; import { SecurityPlugin } from '../../../../security'; @@ -21,6 +15,7 @@ import { SpacesClient } from '../../lib/spaces_client'; import { getSpaceIdFromPath, addSpaceIdToPath } from '../../lib/spaces_url_parser'; import { SpacesConfigType } from '../config'; import { namespaceToSpaceId, spaceIdToNamespace } from '../../lib/utils/namespace'; +import { LegacyAPI } from '../plugin'; type RequestFacade = KibanaRequest | Legacy.Request; @@ -39,26 +34,24 @@ export interface SpacesServiceSetup { } interface SpacesServiceDeps { - http: HttpServiceSetup; - elasticsearch: ElasticsearchServiceSetup; - savedObjects: SavedObjectsService; + http: CoreSetup['http']; + elasticsearch: CoreSetup['elasticsearch']; security: OptionalPlugin; config$: Observable; - spacesAuditLogger: any; + getSpacesAuditLogger(): any; } export class SpacesService { private configSubscription$?: Subscription; - constructor(private readonly log: Logger, private readonly serverBasePath: string) {} + constructor(private readonly log: Logger, private readonly getLegacyAPI: () => LegacyAPI) {} public async setup({ http, elasticsearch, - savedObjects, security, config$, - spacesAuditLogger, + getSpacesAuditLogger, }: SpacesServiceDeps): Promise { const getSpaceId = (request: RequestFacade) => { // Currently utilized by reporting @@ -68,7 +61,7 @@ export class SpacesService { ? (request as Record).getBasePath() : http.basePath.get(request); - const spaceId = getSpaceIdFromPath(basePath, this.serverBasePath); + const spaceId = getSpaceIdFromPath(basePath, this.getServerBasePath()); return spaceId; }; @@ -79,7 +72,7 @@ export class SpacesService { if (!spaceId) { throw new TypeError(`spaceId is required to retrieve base path`); } - return addSpaceIdToPath(this.serverBasePath, spaceId); + return addSpaceIdToPath(this.getServerBasePath(), spaceId); }, isInDefaultSpace: (request: RequestFacade) => { const spaceId = getSpaceId(request); @@ -92,14 +85,14 @@ export class SpacesService { return combineLatest(elasticsearch.adminClient$, config$) .pipe( map(([clusterClient, config]) => { - const internalRepository = savedObjects.getSavedObjectsRepository( + const internalRepository = this.getLegacyAPI().savedObjects.getSavedObjectsRepository( clusterClient.callAsInternalUser, ['space'] ); const callCluster = clusterClient.asScoped(request).callAsCurrentUser; - const callWithRequestRepository = savedObjects.getSavedObjectsRepository( + const callWithRequestRepository = this.getLegacyAPI().savedObjects.getSavedObjectsRepository( callCluster, ['space'] ); @@ -107,7 +100,7 @@ export class SpacesService { const authorization = security.isEnabled ? security.authorization : null; return new SpacesClient( - spacesAuditLogger, + getSpacesAuditLogger(), (message: string) => { this.log.debug(message); }, @@ -131,4 +124,8 @@ export class SpacesService { this.configSubscription$ = undefined; } } + + private getServerBasePath() { + return this.getLegacyAPI().legacyConfig.serverBasePath; + } } diff --git a/x-pack/legacy/plugins/spaces/server/routes/api/__fixtures__/create_test_handler.ts b/x-pack/legacy/plugins/spaces/server/routes/api/__fixtures__/create_test_handler.ts index 6305839f64cf6..d84c79c3d78b7 100644 --- a/x-pack/legacy/plugins/spaces/server/routes/api/__fixtures__/create_test_handler.ts +++ b/x-pack/legacy/plugins/spaces/server/routes/api/__fixtures__/create_test_handler.ts @@ -7,9 +7,8 @@ import * as Rx from 'rxjs'; import { Server } from 'hapi'; import { Legacy } from 'kibana'; -import { KibanaConfig } from 'src/legacy/server/kbn_server'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { httpServiceMock, elasticsearchServiceMock } from 'src/core/server/mocks'; +import { elasticsearchServiceMock, coreMock } from 'src/core/server/mocks'; import { SavedObjectsSchema, SavedObjectsService } from 'src/core/server'; import { Readable } from 'stream'; import { createPromiseFromStreams, createConcatStream } from 'src/legacy/utils/streams'; @@ -20,7 +19,7 @@ import { ExternalRouteDeps } from '../external'; import { SpacesService } from '../../../new_platform/spaces_service'; import { SpacesAuditLogger } from '../../../lib/audit_logger'; import { InternalRouteDeps } from '../v1'; -import { SpacesHttpServiceSetup } from '../../../new_platform/plugin'; +import { LegacyAPI } from '../../../new_platform/plugin'; interface KibanaServer extends Legacy.Server { savedObjects: any; @@ -215,13 +214,22 @@ export function createTestHandler( fatal: jest.fn(), }; - const service = new SpacesService(log, server.config().get('server.basePath')); + const coreSetupMock = coreMock.createSetup(); + + const legacyAPI = { + legacyConfig: { + serverBasePath: mockConfig.get('server.basePath'), + serverDefaultRoute: mockConfig.get('server.defaultRoute'), + }, + savedObjects: server.savedObjects, + } as LegacyAPI; + + const service = new SpacesService(log, () => legacyAPI); const spacesService = await service.setup({ - http: httpServiceMock.createSetupContract(), + http: coreSetupMock.http, elasticsearch: elasticsearchServiceMock.createSetupContract(), - savedObjects: server.savedObjects, security: createOptionalPlugin({ get: () => null }, 'xpack.security', {}, 'security'), - spacesAuditLogger: {} as SpacesAuditLogger, + getSpacesAuditLogger: () => ({} as SpacesAuditLogger), config$: Rx.of({ maxSpaces: 1000 }), }); @@ -240,15 +248,12 @@ export function createTestHandler( }); initApiFn({ - http: ({ - server, - route: server.route.bind(server), - } as unknown) as SpacesHttpServiceSetup, + getLegacyAPI: () => legacyAPI, routePreCheckLicenseFn: pre, savedObjects: server.savedObjects, spacesService, log, - config: mockConfig as KibanaConfig, + legacyRouter: server.route.bind(server), }); teardowns.push(() => server.stop()); diff --git a/x-pack/legacy/plugins/spaces/server/routes/api/external/copy_to_space.ts b/x-pack/legacy/plugins/spaces/server/routes/api/external/copy_to_space.ts index d149d6042932d..be5a921f91340 100644 --- a/x-pack/legacy/plugins/spaces/server/routes/api/external/copy_to_space.ts +++ b/x-pack/legacy/plugins/spaces/server/routes/api/external/copy_to_space.ts @@ -34,9 +34,9 @@ interface ResolveConflictsPayload { } export function initCopyToSpacesApi(deps: ExternalRouteDeps) { - const { http, spacesService, savedObjects, routePreCheckLicenseFn } = deps; + const { legacyRouter, spacesService, savedObjects, routePreCheckLicenseFn } = deps; - http.route({ + legacyRouter({ method: 'POST', path: '/api/spaces/_copy_saved_objects', async handler(request: Legacy.Request, h: Legacy.ResponseToolkit) { @@ -87,7 +87,7 @@ export function initCopyToSpacesApi(deps: ExternalRouteDeps) { }, }); - http.route({ + legacyRouter({ method: 'POST', path: '/api/spaces/_resolve_copy_saved_objects_errors', async handler(request: Legacy.Request, h: Legacy.ResponseToolkit) { diff --git a/x-pack/legacy/plugins/spaces/server/routes/api/external/delete.ts b/x-pack/legacy/plugins/spaces/server/routes/api/external/delete.ts index 6e49fc9904c3f..720e932743e9a 100644 --- a/x-pack/legacy/plugins/spaces/server/routes/api/external/delete.ts +++ b/x-pack/legacy/plugins/spaces/server/routes/api/external/delete.ts @@ -10,9 +10,9 @@ import { SpacesClient } from '../../../lib/spaces_client'; import { ExternalRouteDeps, ExternalRouteRequestFacade } from '.'; export function initDeleteSpacesApi(deps: ExternalRouteDeps) { - const { http, savedObjects, spacesService, routePreCheckLicenseFn } = deps; + const { legacyRouter, savedObjects, spacesService, routePreCheckLicenseFn } = deps; - http.route({ + legacyRouter({ method: 'DELETE', path: '/api/spaces/space/{id}', async handler(request: ExternalRouteRequestFacade, h: any) { diff --git a/x-pack/legacy/plugins/spaces/server/routes/api/external/get.ts b/x-pack/legacy/plugins/spaces/server/routes/api/external/get.ts index fd6e60ccb0b07..310cef5c1069e 100644 --- a/x-pack/legacy/plugins/spaces/server/routes/api/external/get.ts +++ b/x-pack/legacy/plugins/spaces/server/routes/api/external/get.ts @@ -14,9 +14,9 @@ import { SpacesClient } from '../../../lib/spaces_client'; import { ExternalRouteDeps, ExternalRouteRequestFacade } from '.'; export function initGetSpacesApi(deps: ExternalRouteDeps) { - const { http, log, spacesService, savedObjects, routePreCheckLicenseFn } = deps; + const { legacyRouter, log, spacesService, savedObjects, routePreCheckLicenseFn } = deps; - http.route({ + legacyRouter({ method: 'GET', path: '/api/spaces/space', async handler(request: ExternalRouteRequestFacade) { @@ -51,7 +51,7 @@ export function initGetSpacesApi(deps: ExternalRouteDeps) { }, }); - http.route({ + legacyRouter({ method: 'GET', path: '/api/spaces/space/{id}', async handler(request: ExternalRouteRequestFacade) { diff --git a/x-pack/legacy/plugins/spaces/server/routes/api/external/index.ts b/x-pack/legacy/plugins/spaces/server/routes/api/external/index.ts index 5559392dc1fda..72ddc193e5c9f 100644 --- a/x-pack/legacy/plugins/spaces/server/routes/api/external/index.ts +++ b/x-pack/legacy/plugins/spaces/server/routes/api/external/index.ts @@ -13,14 +13,13 @@ import { initGetSpacesApi } from './get'; import { initPostSpacesApi } from './post'; import { initPutSpacesApi } from './put'; import { SpacesServiceSetup } from '../../../new_platform/spaces_service/spaces_service'; -import { SpacesHttpServiceSetup } from '../../../new_platform/plugin'; import { initCopyToSpacesApi } from './copy_to_space'; type Omit = Pick>; interface RouteDeps { xpackMain: XPackMainPlugin; - http: SpacesHttpServiceSetup; + legacyRouter: Legacy.Server['route']; savedObjects: SavedObjectsService; spacesService: SpacesServiceSetup; log: Logger; diff --git a/x-pack/legacy/plugins/spaces/server/routes/api/external/post.ts b/x-pack/legacy/plugins/spaces/server/routes/api/external/post.ts index 372eb743a93f2..6a17b5c5eace6 100644 --- a/x-pack/legacy/plugins/spaces/server/routes/api/external/post.ts +++ b/x-pack/legacy/plugins/spaces/server/routes/api/external/post.ts @@ -12,9 +12,9 @@ import { SpacesClient } from '../../../lib/spaces_client'; import { ExternalRouteDeps, ExternalRouteRequestFacade } from '.'; export function initPostSpacesApi(deps: ExternalRouteDeps) { - const { http, log, spacesService, savedObjects, routePreCheckLicenseFn } = deps; + const { legacyRouter, log, spacesService, savedObjects, routePreCheckLicenseFn } = deps; - http.route({ + legacyRouter({ method: 'POST', path: '/api/spaces/space', async handler(request: ExternalRouteRequestFacade) { diff --git a/x-pack/legacy/plugins/spaces/server/routes/api/external/put.ts b/x-pack/legacy/plugins/spaces/server/routes/api/external/put.ts index 3e1ac4afec684..8e0f7673358d0 100644 --- a/x-pack/legacy/plugins/spaces/server/routes/api/external/put.ts +++ b/x-pack/legacy/plugins/spaces/server/routes/api/external/put.ts @@ -12,9 +12,9 @@ import { SpacesClient } from '../../../lib/spaces_client'; import { ExternalRouteDeps, ExternalRouteRequestFacade } from '.'; export function initPutSpacesApi(deps: ExternalRouteDeps) { - const { http, spacesService, savedObjects, routePreCheckLicenseFn } = deps; + const { legacyRouter, spacesService, savedObjects, routePreCheckLicenseFn } = deps; - http.route({ + legacyRouter({ method: 'PUT', path: '/api/spaces/space/{id}', async handler(request: ExternalRouteRequestFacade) { diff --git a/x-pack/legacy/plugins/spaces/server/routes/api/v1/index.ts b/x-pack/legacy/plugins/spaces/server/routes/api/v1/index.ts index 932c3c869af6b..ddbca3e8e3d71 100644 --- a/x-pack/legacy/plugins/spaces/server/routes/api/v1/index.ts +++ b/x-pack/legacy/plugins/spaces/server/routes/api/v1/index.ts @@ -4,22 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import { KibanaConfig } from 'src/legacy/server/kbn_server'; -import { SavedObjectsService } from 'src/core/server'; +import { Legacy } from 'kibana'; import { XPackMainPlugin } from '../../../../../xpack_main/xpack_main'; import { routePreCheckLicense } from '../../../lib/route_pre_check_license'; import { initInternalSpacesApi } from './spaces'; import { SpacesServiceSetup } from '../../../new_platform/spaces_service/spaces_service'; -import { SpacesHttpServiceSetup } from '../../../new_platform/plugin'; +import { LegacyAPI } from '../../../new_platform/plugin'; type Omit = Pick>; interface RouteDeps { xpackMain: XPackMainPlugin; - http: SpacesHttpServiceSetup; - savedObjects: SavedObjectsService; spacesService: SpacesServiceSetup; - config: KibanaConfig; + getLegacyAPI(): LegacyAPI; + legacyRouter: Legacy.Server['route']; } export interface InternalRouteDeps extends Omit { diff --git a/x-pack/legacy/plugins/spaces/server/routes/api/v1/spaces.ts b/x-pack/legacy/plugins/spaces/server/routes/api/v1/spaces.ts index 8765bd635a45a..3d15044d129e9 100644 --- a/x-pack/legacy/plugins/spaces/server/routes/api/v1/spaces.ts +++ b/x-pack/legacy/plugins/spaces/server/routes/api/v1/spaces.ts @@ -13,16 +13,20 @@ import { getSpaceById } from '../../lib'; import { InternalRouteDeps } from '.'; export function initInternalSpacesApi(deps: InternalRouteDeps) { - const { http, config, spacesService, savedObjects, routePreCheckLicenseFn } = deps; + const { legacyRouter, spacesService, getLegacyAPI, routePreCheckLicenseFn } = deps; - http.route({ + legacyRouter({ method: 'POST', path: '/api/spaces/v1/space/{id}/select', async handler(request: any) { + const { savedObjects, legacyConfig } = getLegacyAPI(); + const { SavedObjectsClient } = savedObjects; const spacesClient: SpacesClient = await spacesService.scopedClient(request); const id = request.params.id; + const basePath = legacyConfig.serverBasePath; + const defaultRoute = legacyConfig.serverDefaultRoute; try { const existingSpace: Space | null = await getSpaceById( spacesClient, @@ -34,11 +38,7 @@ export function initInternalSpacesApi(deps: InternalRouteDeps) { } return { - location: addSpaceIdToPath( - config.get('server.basePath'), - existingSpace.id, - config.get('server.defaultRoute') - ), + location: addSpaceIdToPath(basePath, existingSpace.id, defaultRoute), }; } catch (error) { return wrapError(error); diff --git a/x-pack/legacy/plugins/spaces/server/routes/views/index.ts b/x-pack/legacy/plugins/spaces/server/routes/views/index.ts new file mode 100644 index 0000000000000..a0f72886940a4 --- /dev/null +++ b/x-pack/legacy/plugins/spaces/server/routes/views/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { initSpaceSelectorView } from './space_selector'; diff --git a/x-pack/legacy/plugins/spaces/server/routes/views/space_selector.ts b/x-pack/legacy/plugins/spaces/server/routes/views/space_selector.ts new file mode 100644 index 0000000000000..25c4179b99542 --- /dev/null +++ b/x-pack/legacy/plugins/spaces/server/routes/views/space_selector.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Legacy } from 'kibana'; + +export function initSpaceSelectorView(server: Legacy.Server) { + const spaceSelector = server.getHiddenUiAppById('space_selector'); + + server.route({ + method: 'GET', + path: '/spaces/space_selector', + async handler(request, h) { + return (await h.renderAppWithDefaultConfig(spaceSelector)).takeover(); + }, + }); +} 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 c38bc13684e3d..56969879b6322 100644 --- a/x-pack/test/ui_capabilities/common/services/ui_capabilities.ts +++ b/x-pack/test/ui_capabilities/common/services/ui_capabilities.ts @@ -17,7 +17,7 @@ export interface BasicCredentials { } export enum GetUICapabilitiesFailureReason { - RedirectedToRoot = 'Redirected to Root', + RedirectedToSpaceSelector = 'Redirected to Space Selector', NotFound = 'Not Found', } @@ -61,10 +61,10 @@ export class UICapabilitiesService { headers: requestHeaders, }); - if (response.status === 302 && response.headers.location === '/') { + if (response.status === 302 && response.headers.location === '/spaces/space_selector') { return { success: false, - failureReason: GetUICapabilitiesFailureReason.RedirectedToRoot, + failureReason: GetUICapabilitiesFailureReason.RedirectedToSpaceSelector, }; } 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 ca27a3401cdf7..3ea00890aedeb 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 @@ -79,7 +79,7 @@ export default function catalogueTests({ getService }: FtrProviderContext) { case 'nothing_space_read at everything_space': expect(uiCapabilities.success).to.be(false); expect(uiCapabilities.failureReason).to.be( - GetUICapabilitiesFailureReason.RedirectedToRoot + GetUICapabilitiesFailureReason.RedirectedToSpaceSelector ); break; default: 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 cd96a2192f335..ef3162fe9ddd9 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 @@ -83,7 +83,7 @@ export default function fooTests({ getService }: FtrProviderContext) { case 'nothing_space_read at everything_space': expect(uiCapabilities.success).to.be(false); expect(uiCapabilities.failureReason).to.be( - GetUICapabilitiesFailureReason.RedirectedToRoot + GetUICapabilitiesFailureReason.RedirectedToSpaceSelector ); break; default: 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 9b54a1b9c925d..1b9c1daf90282 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 @@ -72,7 +72,7 @@ export default function navLinksTests({ getService }: FtrProviderContext) { case 'nothing_space_read at everything_space': expect(uiCapabilities.success).to.be(false); expect(uiCapabilities.failureReason).to.be( - GetUICapabilitiesFailureReason.RedirectedToRoot + GetUICapabilitiesFailureReason.RedirectedToSpaceSelector ); break; default: