diff --git a/x-pack/legacy/plugins/security/server/routes/api/v1/authenticate.js b/x-pack/legacy/plugins/security/server/routes/api/v1/authenticate.js index 5e2bfce7ada13..ccd4e1c3a82c6 100644 --- a/x-pack/legacy/plugins/security/server/routes/api/v1/authenticate.js +++ b/x-pack/legacy/plugins/security/server/routes/api/v1/authenticate.js @@ -12,6 +12,12 @@ import { KibanaRequest } from '../../../../../../../../src/core/server'; import { createCSPRuleString } from '../../../../../../../../src/legacy/server/csp'; export function initAuthenticateApi({ authc: { login, logout }, config }, server) { + function prepareCustomResourceResponse(response, contentType) { + return response + .header('cache-control', 'private, no-cache, no-store') + .header('content-security-policy', createCSPRuleString(server.config().get('csp.rules'))) + .type(contentType); + } server.route({ method: 'POST', @@ -93,22 +99,36 @@ export function initAuthenticateApi({ authc: { login, logout }, config }, server path: '/api/security/v1/oidc/implicit', config: { auth: false }, async handler(request, h) { - const legacyConfig = server.config(); - const basePath = legacyConfig.get('server.basePath'); - - const cspRulesHeader = createCSPRuleString(legacyConfig.get('csp.rules')); - return h.response(` - - Kibana OpenID Connect Login - + `), + 'text/html' + ); + } + }); + + /** + * The route that accompanies `/api/security/v1/oidc/implicit` and renders a JavaScript snippet + * that extracts fragment part from the URL and send it to the `/api/security/v1/oidc` route. + * We need this separate endpoint because of default CSP policy that forbids inline scripts. + */ + server.route({ + method: 'GET', + path: '/api/security/v1/oidc/implicit.js', + config: { auth: false }, + async handler(request, h) { + return prepareCustomResourceResponse( + h.response(` window.location.replace( - '${basePath}/api/security/v1/oidc?authenticationResponseURI=' + encodeURIComponent(window.location.href) + '${server.config().get('server.basePath')}/api/security/v1/oidc?authenticationResponseURI=' + + encodeURIComponent(window.location.href) ); - - `) - .header('cache-control', 'private, no-cache, no-store') - .header('content-security-policy', cspRulesHeader) - .type('text/html'); + `), + 'text/javascript' + ); } }); diff --git a/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts b/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts index 4689ed447632f..0c77ff3a0640e 100644 --- a/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts +++ b/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts @@ -7,12 +7,14 @@ import expect from '@kbn/expect'; import { JSDOM } from 'jsdom'; import request, { Cookie } from 'request'; +import { format as formatURL } from 'url'; import { createTokens, getStateAndNonce } from '../../fixtures/oidc_tools'; import { FtrProviderContext } from '../../ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); + const config = getService('config'); describe('OpenID Connect Implicit Flow authentication', () => { describe('finishing handshake', () => { @@ -31,22 +33,30 @@ export default function({ getService }: FtrProviderContext) { it('should return an HTML page that will parse URL fragment', async () => { const response = await supertest.get('/api/security/v1/oidc/implicit').expect(200); const dom = new JSDOM(response.text, { + url: formatURL({ ...config.get('servers.kibana'), auth: false }), runScripts: 'dangerously', + resources: 'usable', beforeParse(window) { // JSDOM doesn't support changing of `window.location` and throws an exception if script - // tries to do that and we have to workaround this behaviour. - Object.defineProperty(window, 'location', { - value: { - href: - 'https://kibana.com/api/security/v1/oidc/implicit#token=some_token&access_token=some_access_token', - replace(newLocation: string) { - this.href = newLocation; + // tries to do that and we have to workaround this behaviour. We also need to wait until our + // script is loaded and executed, __isScriptExecuted__ is used exactly for that. + (window as Record).__isScriptExecuted__ = new Promise(resolve => { + Object.defineProperty(window, 'location', { + value: { + href: + 'https://kibana.com/api/security/v1/oidc/implicit#token=some_token&access_token=some_access_token', + replace(newLocation: string) { + this.href = newLocation; + resolve(); + }, }, - }, + }); }); }, }); + await (dom.window as Record).__isScriptExecuted__; + // Check that proxy page is returned with proper headers. expect(response.headers['content-type']).to.be('text/html; charset=utf-8'); expect(response.headers['cache-control']).to.be('private, no-cache, no-store'); diff --git a/x-pack/test/oidc_api_integration/implicit_flow.config.ts b/x-pack/test/oidc_api_integration/implicit_flow.config.ts index a7854488097a6..93f2349a40099 100644 --- a/x-pack/test/oidc_api_integration/implicit_flow.config.ts +++ b/x-pack/test/oidc_api_integration/implicit_flow.config.ts @@ -20,17 +20,20 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) { esTestCluster: { ...oidcAPITestsConfig.get('esTestCluster'), - serverArgs: oidcAPITestsConfig.get('esTestCluster.serverArgs').map((arg: string) => { - if (arg.startsWith('xpack.security.authc.realms.oidc.oidc1.rp.response_type')) { - return 'xpack.security.authc.realms.oidc.oidc1.rp.response_type=id_token token'; - } + serverArgs: oidcAPITestsConfig + .get('esTestCluster.serverArgs') + .reduce((serverArgs: string[], arg: string) => { + // We should change `response_type` to `id_token token` and get rid of unnecessary `token_endpoint`. + if (arg.startsWith('xpack.security.authc.realms.oidc.oidc1.rp.response_type')) { + serverArgs.push( + 'xpack.security.authc.realms.oidc.oidc1.rp.response_type=id_token token' + ); + } else if (!arg.startsWith('xpack.security.authc.realms.oidc.oidc1.op.token_endpoint')) { + serverArgs.push(arg); + } - if (arg.startsWith('xpack.security.authc.realms.oidc.oidc1.op.token_endpoint')) { - return 'xpack.security.authc.realms.oidc.oidc1.op.token_endpoint=should_not_be_used'; - } - - return arg; - }), + return serverArgs; + }, []), }, }; }