From f84ac082252e6e2a952664e2ee7aa4d653cf28ad Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 22 Jan 2020 09:16:47 +0100 Subject: [PATCH] Migrate session storage short url handling (#55021) (#55393) --- .../state_session_storage_redirect/index.js | 32 ------- .../package.json | 5 -- src/legacy/server/kbn_server.js | 4 - src/legacy/server/url_shortening/index.js | 20 ----- .../routes/lib/short_url_assert_valid.js | 41 --------- .../routes/lib/short_url_assert_valid.test.js | 63 -------------- .../routes/lib/short_url_error.js | 26 ------ .../routes/lib/short_url_error.test.js | 67 --------------- .../routes/lib/short_url_lookup.js | 43 ---------- .../routes/lib/short_url_lookup.test.js | 84 ------------------- .../url_shortening/url_shortening_mixin.js | 23 ----- src/plugins/newsfeed/public/plugin.tsx | 12 ++- .../share/common/short_url_routes.ts} | 16 ++-- src/plugins/share/public/lib/url_shortener.ts | 5 +- src/plugins/share/public/plugin.test.ts | 17 +++- src/plugins/share/public/plugin.ts | 6 +- .../services/short_url_redirect_app.test.ts | 46 ++++++++++ .../services/short_url_redirect_app.ts} | 39 +++++---- .../share/server/routes/create_routes.ts | 2 + .../share/server/routes/get.ts} | 49 +++++++---- src/plugins/share/server/routes/goto.ts | 10 ++- .../share/server/routes/shorten_url.ts | 3 +- 22 files changed, 152 insertions(+), 461 deletions(-) delete mode 100644 src/legacy/core_plugins/state_session_storage_redirect/index.js delete mode 100644 src/legacy/core_plugins/state_session_storage_redirect/package.json delete mode 100644 src/legacy/server/url_shortening/index.js delete mode 100644 src/legacy/server/url_shortening/routes/lib/short_url_assert_valid.js delete mode 100644 src/legacy/server/url_shortening/routes/lib/short_url_assert_valid.test.js delete mode 100644 src/legacy/server/url_shortening/routes/lib/short_url_error.js delete mode 100644 src/legacy/server/url_shortening/routes/lib/short_url_error.test.js delete mode 100644 src/legacy/server/url_shortening/routes/lib/short_url_lookup.js delete mode 100644 src/legacy/server/url_shortening/routes/lib/short_url_lookup.test.js delete mode 100644 src/legacy/server/url_shortening/url_shortening_mixin.js rename src/{legacy/server/url_shortening/routes/create_routes.js => plugins/share/common/short_url_routes.ts} (67%) create mode 100644 src/plugins/share/public/services/short_url_redirect_app.test.ts rename src/{legacy/core_plugins/state_session_storage_redirect/public/index.js => plugins/share/public/services/short_url_redirect_app.ts} (50%) rename src/{legacy/server/url_shortening/routes/goto.js => plugins/share/server/routes/get.ts} (55%) diff --git a/src/legacy/core_plugins/state_session_storage_redirect/index.js b/src/legacy/core_plugins/state_session_storage_redirect/index.js deleted file mode 100644 index 2d4d7c97232c..000000000000 --- a/src/legacy/core_plugins/state_session_storage_redirect/index.js +++ /dev/null @@ -1,32 +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 default function(kibana) { - return new kibana.Plugin({ - uiExports: { - app: { - require: ['kibana'], - title: 'Redirecting', - id: 'stateSessionStorageRedirect', - main: 'plugins/state_session_storage_redirect', - hidden: true, - }, - }, - }); -} diff --git a/src/legacy/core_plugins/state_session_storage_redirect/package.json b/src/legacy/core_plugins/state_session_storage_redirect/package.json deleted file mode 100644 index 21956e5d76d5..000000000000 --- a/src/legacy/core_plugins/state_session_storage_redirect/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "state_session_storage_redirect", - "version": "kibana", - "description": "When using the state:storeInSessionStorage setting with the short-urls, we need some way to get the full URL's hashed states into sessionStorage, this app will grab the URL from the injected state and and put the URL hashed states into sessionStorage before redirecting the user." -} diff --git a/src/legacy/server/kbn_server.js b/src/legacy/server/kbn_server.js index 8ab55d4c517b..6991527a9503 100644 --- a/src/legacy/server/kbn_server.js +++ b/src/legacy/server/kbn_server.js @@ -36,7 +36,6 @@ import * as Plugins from './plugins'; import { indexPatternsMixin } from './index_patterns'; import { savedObjectsMixin } from './saved_objects/saved_objects_mixin'; import { capabilitiesMixin } from './capabilities'; -import { urlShorteningMixin } from './url_shortening'; import { serverExtensionsMixin } from './server_extensions'; import { uiMixin } from '../ui'; import { sassMixin } from './sass'; @@ -123,9 +122,6 @@ export default class KbnServer { // setup capabilities routes capabilitiesMixin, - // setup routes for short urls - urlShorteningMixin, - // ensure that all bundles are built, or that the // watch bundle server is running optimizeMixin, diff --git a/src/legacy/server/url_shortening/index.js b/src/legacy/server/url_shortening/index.js deleted file mode 100644 index 031af0618d7b..000000000000 --- a/src/legacy/server/url_shortening/index.js +++ /dev/null @@ -1,20 +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 { urlShorteningMixin } from './url_shortening_mixin'; diff --git a/src/legacy/server/url_shortening/routes/lib/short_url_assert_valid.js b/src/legacy/server/url_shortening/routes/lib/short_url_assert_valid.js deleted file mode 100644 index ff2b0f214e5e..000000000000 --- a/src/legacy/server/url_shortening/routes/lib/short_url_assert_valid.js +++ /dev/null @@ -1,41 +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 { parse } from 'url'; -import { trim } from 'lodash'; -import Boom from 'boom'; - -export function shortUrlAssertValid(url) { - const { protocol, hostname, pathname } = parse(url); - - if (protocol) { - throw Boom.notAcceptable(`Short url targets cannot have a protocol, found "${protocol}"`); - } - - if (hostname) { - throw Boom.notAcceptable(`Short url targets cannot have a hostname, found "${hostname}"`); - } - - const pathnameParts = trim(pathname, '/').split('/'); - if (pathnameParts.length !== 2) { - throw Boom.notAcceptable( - `Short url target path must be in the format "/app/{{appId}}", found "${pathname}"` - ); - } -} diff --git a/src/legacy/server/url_shortening/routes/lib/short_url_assert_valid.test.js b/src/legacy/server/url_shortening/routes/lib/short_url_assert_valid.test.js deleted file mode 100644 index f83073e6aefe..000000000000 --- a/src/legacy/server/url_shortening/routes/lib/short_url_assert_valid.test.js +++ /dev/null @@ -1,63 +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 { shortUrlAssertValid } from './short_url_assert_valid'; - -describe('shortUrlAssertValid()', () => { - const invalid = [ - ['protocol', 'http://localhost:5601/app/kibana'], - ['protocol', 'https://localhost:5601/app/kibana'], - ['protocol', 'mailto:foo@bar.net'], - ['protocol', 'javascript:alert("hi")'], // eslint-disable-line no-script-url - ['hostname', 'localhost/app/kibana'], - ['hostname and port', 'local.host:5601/app/kibana'], - ['hostname and auth', 'user:pass@localhost.net/app/kibana'], - ['path traversal', '/app/../../not-kibana'], - ['deep path', '/app/kibana/foo'], - ['deep path', '/app/kibana/foo/bar'], - ['base path', '/base/app/kibana'], - ]; - - invalid.forEach(([desc, url]) => { - it(`fails when url has ${desc}`, () => { - try { - shortUrlAssertValid(url); - throw new Error(`expected assertion to throw`); - } catch (err) { - if (!err || !err.isBoom) { - throw err; - } - } - }); - }); - - const valid = [ - '/app/kibana', - '/app/monitoring#angular/route', - '/app/text#document-id', - '/app/some?with=query', - '/app/some?with=query#and-a-hash', - ]; - - valid.forEach(url => { - it(`allows ${url}`, () => { - shortUrlAssertValid(url); - }); - }); -}); diff --git a/src/legacy/server/url_shortening/routes/lib/short_url_error.js b/src/legacy/server/url_shortening/routes/lib/short_url_error.js deleted file mode 100644 index ed44ba21aa4c..000000000000 --- a/src/legacy/server/url_shortening/routes/lib/short_url_error.js +++ /dev/null @@ -1,26 +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 Boom from 'boom'; - -export function handleShortUrlError(error) { - return Boom.boomify(error, { - statusCode: error.statusCode || error.status || 500, - }); -} diff --git a/src/legacy/server/url_shortening/routes/lib/short_url_error.test.js b/src/legacy/server/url_shortening/routes/lib/short_url_error.test.js deleted file mode 100644 index 4eca6320ec83..000000000000 --- a/src/legacy/server/url_shortening/routes/lib/short_url_error.test.js +++ /dev/null @@ -1,67 +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 _ from 'lodash'; -import { handleShortUrlError } from './short_url_error'; - -function createErrorWithStatus(status) { - const error = new Error(); - error.status = status; - return error; -} - -function createErrorWithStatusCode(statusCode) { - const error = new Error(); - error.statusCode = statusCode; - return error; -} - -describe('handleShortUrlError()', () => { - const caughtErrorsWithStatus = [ - createErrorWithStatus(401), - createErrorWithStatus(403), - createErrorWithStatus(404), - ]; - - const caughtErrorsWithStatusCode = [ - createErrorWithStatusCode(401), - createErrorWithStatusCode(403), - createErrorWithStatusCode(404), - ]; - - const uncaughtErrors = [new Error(), createErrorWithStatus(500), createErrorWithStatusCode(500)]; - - caughtErrorsWithStatus.forEach(err => { - it(`should handle errors with status of ${err.status}`, function() { - expect(_.get(handleShortUrlError(err), 'output.statusCode')).toBe(err.status); - }); - }); - - caughtErrorsWithStatusCode.forEach(err => { - it(`should handle errors with statusCode of ${err.statusCode}`, function() { - expect(_.get(handleShortUrlError(err), 'output.statusCode')).toBe(err.statusCode); - }); - }); - - uncaughtErrors.forEach(err => { - it(`should not handle unknown errors`, function() { - expect(_.get(handleShortUrlError(err), 'output.statusCode')).toBe(500); - }); - }); -}); diff --git a/src/legacy/server/url_shortening/routes/lib/short_url_lookup.js b/src/legacy/server/url_shortening/routes/lib/short_url_lookup.js deleted file mode 100644 index a8a01d1433a7..000000000000 --- a/src/legacy/server/url_shortening/routes/lib/short_url_lookup.js +++ /dev/null @@ -1,43 +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 { get } from 'lodash'; - -export function shortUrlLookupProvider(server) { - async function updateMetadata(doc, req) { - try { - await req.getSavedObjectsClient().update('url', doc.id, { - accessDate: new Date(), - accessCount: get(doc, 'attributes.accessCount', 0) + 1, - }); - } catch (err) { - server.log('Warning: Error updating url metadata', err); - //swallow errors. It isn't critical if there is no update. - } - } - - return { - async getUrl(id, req) { - const doc = await req.getSavedObjectsClient().get('url', id); - updateMetadata(doc, req); - - return doc.attributes.url; - }, - }; -} diff --git a/src/legacy/server/url_shortening/routes/lib/short_url_lookup.test.js b/src/legacy/server/url_shortening/routes/lib/short_url_lookup.test.js deleted file mode 100644 index e8bf72a142d1..000000000000 --- a/src/legacy/server/url_shortening/routes/lib/short_url_lookup.test.js +++ /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 sinon from 'sinon'; -import { shortUrlLookupProvider } from './short_url_lookup'; -import { SavedObjectsClient } from '../../../../../core/server'; - -describe('shortUrlLookupProvider', () => { - const ID = 'bf00ad16941fc51420f91a93428b27a0'; - const TYPE = 'url'; - const URL = 'http://elastic.co'; - const server = { log: sinon.stub() }; - const sandbox = sinon.createSandbox(); - - let savedObjectsClient; - let req; - let shortUrl; - - beforeEach(() => { - savedObjectsClient = { - get: sandbox.stub(), - create: sandbox.stub().returns(Promise.resolve({ id: ID })), - update: sandbox.stub(), - errors: SavedObjectsClient.errors, - }; - - req = { getSavedObjectsClient: () => savedObjectsClient }; - shortUrl = shortUrlLookupProvider(server); - }); - - afterEach(() => { - sandbox.restore(); - }); - - describe('getUrl', () => { - beforeEach(() => { - const attributes = { accessCount: 2, url: URL }; - savedObjectsClient.get.returns({ id: ID, attributes }); - }); - - it('provides the ID to savedObjectsClient', async () => { - await shortUrl.getUrl(ID, req); - - sinon.assert.calledOnce(savedObjectsClient.get); - const [type, id] = savedObjectsClient.get.getCall(0).args; - - expect(type).toEqual(TYPE); - expect(id).toEqual(ID); - }); - - it('returns the url', async () => { - const response = await shortUrl.getUrl(ID, req); - expect(response).toEqual(URL); - }); - - it('increments accessCount', async () => { - await shortUrl.getUrl(ID, req); - - sinon.assert.calledOnce(savedObjectsClient.update); - const [type, id, attributes] = savedObjectsClient.update.getCall(0).args; - - expect(type).toEqual(TYPE); - expect(id).toEqual(ID); - expect(Object.keys(attributes).sort()).toEqual(['accessCount', 'accessDate']); - expect(attributes.accessCount).toEqual(3); - }); - }); -}); diff --git a/src/legacy/server/url_shortening/url_shortening_mixin.js b/src/legacy/server/url_shortening/url_shortening_mixin.js deleted file mode 100644 index 867898cac845..000000000000 --- a/src/legacy/server/url_shortening/url_shortening_mixin.js +++ /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. - */ -import { createRoutes } from './routes/create_routes'; - -export function urlShorteningMixin(kbnServer, server) { - createRoutes(server); -} diff --git a/src/plugins/newsfeed/public/plugin.tsx b/src/plugins/newsfeed/public/plugin.tsx index 5ea5e5b32471..c4e042fe452f 100644 --- a/src/plugins/newsfeed/public/plugin.tsx +++ b/src/plugins/newsfeed/public/plugin.tsx @@ -23,7 +23,7 @@ import ReactDOM from 'react-dom'; import React from 'react'; import { I18nProvider } from '@kbn/i18n/react'; import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; -import { NewsfeedPluginInjectedConfig } from '../types'; +import { FetchResult, NewsfeedPluginInjectedConfig } from '../types'; import { NewsfeedNavButton, NewsfeedApiFetchResult } from './components/newsfeed_header_nav_button'; import { getApi } from './lib/api'; @@ -54,10 +54,14 @@ export class NewsfeedPublicPlugin implements Plugin { private fetchNewsfeed(core: CoreStart) { const { http, injectedMetadata } = core; - const config = injectedMetadata.getInjectedVar( - 'newsfeed' - ) as NewsfeedPluginInjectedConfig['newsfeed']; + const config = injectedMetadata.getInjectedVar('newsfeed') as + | NewsfeedPluginInjectedConfig['newsfeed'] + | undefined; + if (!config) { + // running in new platform, injected metadata not available + return new Rx.Observable(); + } return getApi(http, config, this.kibanaVersion).pipe( takeUntil(this.stop$), // stop the interval when stop method is called catchError(() => Rx.of(null)) // do not throw error diff --git a/src/legacy/server/url_shortening/routes/create_routes.js b/src/plugins/share/common/short_url_routes.ts similarity index 67% rename from src/legacy/server/url_shortening/routes/create_routes.js rename to src/plugins/share/common/short_url_routes.ts index 9540e7441a26..7b42534de2ab 100644 --- a/src/legacy/server/url_shortening/routes/create_routes.js +++ b/src/plugins/share/common/short_url_routes.ts @@ -17,11 +17,15 @@ * under the License. */ -import { shortUrlLookupProvider } from './lib/short_url_lookup'; -import { createGotoRoute } from './goto'; +export const GOTO_PREFIX = '/goto'; -export function createRoutes(server) { - const shortUrlLookup = shortUrlLookupProvider(server); +export const getUrlIdFromGotoRoute = (path: string) => + path.match(new RegExp(`${GOTO_PREFIX}/(.*)$`))?.[1]; - server.route(createGotoRoute({ server, shortUrlLookup })); -} +export const getGotoPath = (urlId: string) => `${GOTO_PREFIX}/${urlId}`; + +export const GETTER_PREFIX = '/api/short_url'; + +export const getUrlPath = (urlId: string) => `${GETTER_PREFIX}/${urlId}`; + +export const CREATE_PATH = '/api/shorten_url'; diff --git a/src/plugins/share/public/lib/url_shortener.ts b/src/plugins/share/public/lib/url_shortener.ts index 29d91bdb1aae..f2259f1fabf7 100644 --- a/src/plugins/share/public/lib/url_shortener.ts +++ b/src/plugins/share/public/lib/url_shortener.ts @@ -19,6 +19,7 @@ import url from 'url'; import { HttpStart } from 'kibana/public'; +import { CREATE_PATH, getGotoPath } from '../../common/short_url_routes'; export async function shortenUrl( absoluteUrl: string, @@ -34,10 +35,10 @@ export async function shortenUrl( const body = JSON.stringify({ url: relativeUrl }); - const resp = await post('/api/shorten_url', { body }); + const resp = await post(CREATE_PATH, { body }); return url.format({ protocol: parsedUrl.protocol, host: parsedUrl.host, - pathname: `${basePath}/goto/${resp.urlId}`, + pathname: `${basePath}${getGotoPath(resp.urlId)}`, }); } diff --git a/src/plugins/share/public/plugin.test.ts b/src/plugins/share/public/plugin.test.ts index 5610490be33b..730814fe9ed2 100644 --- a/src/plugins/share/public/plugin.test.ts +++ b/src/plugins/share/public/plugin.test.ts @@ -20,6 +20,7 @@ import { registryMock, managerMock } from './plugin.test.mocks'; import { SharePlugin } from './plugin'; import { CoreStart } from 'kibana/public'; +import { coreMock } from '../../../core/public/mocks'; describe('SharePlugin', () => { beforeEach(() => { @@ -30,16 +31,28 @@ describe('SharePlugin', () => { describe('setup', () => { test('wires up and returns registry', async () => { - const setup = await new SharePlugin().setup(); + const coreSetup = coreMock.createSetup(); + const setup = await new SharePlugin().setup(coreSetup); expect(registryMock.setup).toHaveBeenCalledWith(); expect(setup.register).toBeDefined(); }); + + test('registers redirect app', async () => { + const coreSetup = coreMock.createSetup(); + await new SharePlugin().setup(coreSetup); + expect(coreSetup.application.register).toHaveBeenCalledWith( + expect.objectContaining({ + id: 'short_url_redirect', + }) + ); + }); }); describe('start', () => { test('wires up and returns show function, but not registry', async () => { + const coreSetup = coreMock.createSetup(); const service = new SharePlugin(); - await service.setup(); + await service.setup(coreSetup); const start = await service.start({} as CoreStart); expect(registryMock.start).toHaveBeenCalled(); expect(managerMock.start).toHaveBeenCalledWith( diff --git a/src/plugins/share/public/plugin.ts b/src/plugins/share/public/plugin.ts index 6d78211cf995..01c248624950 100644 --- a/src/plugins/share/public/plugin.ts +++ b/src/plugins/share/public/plugin.ts @@ -17,15 +17,17 @@ * under the License. */ -import { CoreStart, Plugin } from 'src/core/public'; +import { CoreSetup, CoreStart, Plugin } from 'src/core/public'; import { ShareMenuManager, ShareMenuManagerStart } from './services'; import { ShareMenuRegistry, ShareMenuRegistrySetup } from './services'; +import { createShortUrlRedirectApp } from './services/short_url_redirect_app'; export class SharePlugin implements Plugin { private readonly shareMenuRegistry = new ShareMenuRegistry(); private readonly shareContextMenu = new ShareMenuManager(); - public async setup() { + public async setup(core: CoreSetup) { + core.application.register(createShortUrlRedirectApp(core, window.location)); return { ...this.shareMenuRegistry.setup(), }; diff --git a/src/plugins/share/public/services/short_url_redirect_app.test.ts b/src/plugins/share/public/services/short_url_redirect_app.test.ts new file mode 100644 index 000000000000..206e637451ec --- /dev/null +++ b/src/plugins/share/public/services/short_url_redirect_app.test.ts @@ -0,0 +1,46 @@ +/* + * 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 { createShortUrlRedirectApp } from './short_url_redirect_app'; +import { coreMock } from '../../../../core/public/mocks'; +import { hashUrl } from '../../../kibana_utils/public'; + +jest.mock('../../../kibana_utils/public', () => ({ hashUrl: jest.fn(x => `${x}/hashed`) })); + +describe('short_url_redirect_app', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should fetch url and redirect to hashed version', async () => { + const coreSetup = coreMock.createSetup({ basePath: 'base' }); + coreSetup.http.get.mockResolvedValueOnce({ url: '/app/abc' }); + const locationMock = { pathname: '/base/goto/12345', href: '' } as Location; + + const { mount } = createShortUrlRedirectApp(coreSetup, locationMock); + await mount(); + + // check for fetching the complete URL + expect(coreSetup.http.get).toHaveBeenCalledWith('/api/short_url/12345'); + // check for hashing the URL returned from the server + expect(hashUrl).toHaveBeenCalledWith('/app/abc'); + // check for redirecting to the prepended path + expect(locationMock.href).toEqual('base/app/abc/hashed'); + }); +}); diff --git a/src/legacy/core_plugins/state_session_storage_redirect/public/index.js b/src/plugins/share/public/services/short_url_redirect_app.ts similarity index 50% rename from src/legacy/core_plugins/state_session_storage_redirect/public/index.js rename to src/plugins/share/public/services/short_url_redirect_app.ts index 701a5736c7d3..6f72b711f660 100644 --- a/src/legacy/core_plugins/state_session_storage_redirect/public/index.js +++ b/src/plugins/share/public/services/short_url_redirect_app.ts @@ -17,24 +17,29 @@ * under the License. */ -import chrome from 'ui/chrome'; -import { hashUrl } from '../../../../plugins/kibana_utils/public'; -import uiRoutes from 'ui/routes'; -import { fatalError } from 'ui/notify'; +import { CoreSetup } from 'kibana/public'; +import { getUrlIdFromGotoRoute, getUrlPath, GOTO_PREFIX } from '../../common/short_url_routes'; +import { hashUrl } from '../../../kibana_utils/public'; -uiRoutes.enable(); -uiRoutes.when('/', { - resolve: { - url: function(AppState, globalState, $window) { - const redirectUrl = chrome.getInjected('redirectUrl'); - try { - const hashedUrl = hashUrl(redirectUrl); - const url = chrome.addBasePath(hashedUrl); +export const createShortUrlRedirectApp = (core: CoreSetup, location: Location) => ({ + id: 'short_url_redirect', + appRoute: GOTO_PREFIX, + chromeless: true, + title: 'Short URL Redirect', + async mount() { + const urlId = getUrlIdFromGotoRoute(location.pathname); - $window.location = url; - } catch (e) { - fatalError(e); - } - }, + if (!urlId) { + throw new Error('Url id not present in path'); + } + + const response = await core.http.get<{ url: string }>(getUrlPath(urlId)); + const redirectUrl = response.url; + const hashedUrl = hashUrl(redirectUrl); + const url = core.http.basePath.prepend(hashedUrl); + + location.href = url; + + return () => {}; }, }); diff --git a/src/plugins/share/server/routes/create_routes.ts b/src/plugins/share/server/routes/create_routes.ts index bd4b6fdb0879..22d10c25197c 100644 --- a/src/plugins/share/server/routes/create_routes.ts +++ b/src/plugins/share/server/routes/create_routes.ts @@ -22,11 +22,13 @@ import { CoreSetup, Logger } from 'kibana/server'; import { shortUrlLookupProvider } from './lib/short_url_lookup'; import { createGotoRoute } from './goto'; import { createShortenUrlRoute } from './shorten_url'; +import { createGetterRoute } from './get'; export function createRoutes({ http }: CoreSetup, logger: Logger) { const shortUrlLookup = shortUrlLookupProvider({ logger }); const router = http.createRouter(); createGotoRoute({ router, shortUrlLookup, http }); + createGetterRoute({ router, shortUrlLookup, http }); createShortenUrlRoute({ router, shortUrlLookup }); } diff --git a/src/legacy/server/url_shortening/routes/goto.js b/src/plugins/share/server/routes/get.ts similarity index 55% rename from src/legacy/server/url_shortening/routes/goto.js rename to src/plugins/share/server/routes/get.ts index 7a874786423d..d6b191341dbe 100644 --- a/src/legacy/server/url_shortening/routes/goto.js +++ b/src/plugins/share/server/routes/get.ts @@ -17,23 +17,40 @@ * under the License. */ -import { handleShortUrlError } from './lib/short_url_error'; -import { shortUrlAssertValid } from './lib/short_url_assert_valid'; +import { CoreSetup, IRouter } from 'kibana/server'; +import { schema } from '@kbn/config-schema'; -export const createGotoRoute = ({ server, shortUrlLookup }) => ({ - method: 'GET', - path: '/goto_LP/{urlId}', - handler: async function(request, h) { - try { - const url = await shortUrlLookup.getUrl(request.params.urlId, request); +import { shortUrlAssertValid } from './lib/short_url_assert_valid'; +import { ShortUrlLookupService } from './lib/short_url_lookup'; +import { getUrlPath } from '../../common/short_url_routes'; + +export const createGetterRoute = ({ + router, + shortUrlLookup, + http, +}: { + router: IRouter; + shortUrlLookup: ShortUrlLookupService; + http: CoreSetup['http']; +}) => { + router.get( + { + path: getUrlPath('{urlId}'), + validate: { + params: schema.object({ urlId: schema.string() }), + }, + }, + router.handleLegacyErrors(async function(context, request, response) { + const url = await shortUrlLookup.getUrl(request.params.urlId, { + savedObjects: context.core.savedObjects.client, + }); shortUrlAssertValid(url); - const app = server.getHiddenUiAppById('stateSessionStorageRedirect'); - return h.renderApp(app, { - redirectUrl: url, + return response.ok({ + body: { + url, + }, }); - } catch (err) { - throw handleShortUrlError(err); - } - }, -}); + }) + ); +}; diff --git a/src/plugins/share/server/routes/goto.ts b/src/plugins/share/server/routes/goto.ts index 7343dc1bd34a..5c3a4e441099 100644 --- a/src/plugins/share/server/routes/goto.ts +++ b/src/plugins/share/server/routes/goto.ts @@ -22,6 +22,7 @@ import { schema } from '@kbn/config-schema'; import { shortUrlAssertValid } from './lib/short_url_assert_valid'; import { ShortUrlLookupService } from './lib/short_url_lookup'; +import { getGotoPath } from '../../common/short_url_routes'; export const createGotoRoute = ({ router, @@ -34,7 +35,7 @@ export const createGotoRoute = ({ }) => { router.get( { - path: '/goto/{urlId}', + path: getGotoPath('{urlId}'), validate: { params: schema.object({ urlId: schema.string() }), }, @@ -54,10 +55,13 @@ export const createGotoRoute = ({ }, }); } - return response.redirected({ + const body = await context.core.rendering.render(); + + return response.ok({ headers: { - location: http.basePath.prepend('/goto_LP/' + request.params.urlId), + 'content-security-policy': http.csp.header, }, + body, }); }) ); diff --git a/src/plugins/share/server/routes/shorten_url.ts b/src/plugins/share/server/routes/shorten_url.ts index 116b90c6971c..41570f8a5f45 100644 --- a/src/plugins/share/server/routes/shorten_url.ts +++ b/src/plugins/share/server/routes/shorten_url.ts @@ -22,6 +22,7 @@ import { schema } from '@kbn/config-schema'; import { shortUrlAssertValid } from './lib/short_url_assert_valid'; import { ShortUrlLookupService } from './lib/short_url_lookup'; +import { CREATE_PATH } from '../../common/short_url_routes'; export const createShortenUrlRoute = ({ shortUrlLookup, @@ -32,7 +33,7 @@ export const createShortenUrlRoute = ({ }) => { router.post( { - path: '/api/shorten_url', + path: CREATE_PATH, validate: { body: schema.object({ url: schema.string() }), },