"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.defineOIDCRoutes = defineOIDCRoutes;
var _configSchema = require("@kbn/config-schema");
var _i18n = require("@kbn/i18n");
var _authentication = require("../../authentication");
var _errors = require("../../errors");
var _licensed_route_handler = require("../licensed_route_handler");
var _tags = require("../tags");
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
/**
* Defines routes required for SAML authentication.
*/
function defineOIDCRoutes({
router,
httpResources,
logger,
getAuthenticationService,
basePath
}) {
// Generate two identical routes with new and deprecated URL and issue a warning if route with deprecated URL is ever used.
for (const path of ['/api/security/oidc/implicit', '/api/security/v1/oidc/implicit']) {
/**
* The route should be configured as a redirect URI in OP when OpenID Connect implicit flow
* is used, so that we can extract authentication response from URL fragment and send it to
* the `/api/security/oidc/callback` route.
*/
httpResources.register({
path,
validate: false,
options: {
authRequired: false
}
}, (context, request, response) => {
const serverBasePath = basePath.serverBasePath;
if (path === '/api/security/v1/oidc/implicit') {
logger.warn(`The "${serverBasePath}${path}" URL is deprecated and will stop working in the next major version, please use "${serverBasePath}/api/security/oidc/implicit" URL instead.`, {
tags: ['deprecation']
});
}
return response.renderHtml({
body: `
Kibana OpenID Connect Login
`
});
});
}
/**
* The route that accompanies `/api/security/oidc/implicit` and renders a JavaScript snippet
* that extracts fragment part from the URL and send it to the `/api/security/oidc/callback` route.
* We need this separate endpoint because of default CSP policy that forbids inline scripts.
*/
httpResources.register({
path: '/internal/security/oidc/implicit.js',
validate: false,
options: {
authRequired: false
}
}, (context, request, response) => {
const serverBasePath = basePath.serverBasePath;
return response.renderJs({
body: `
window.location.replace(
'${serverBasePath}/api/security/oidc/callback?authenticationResponseURI=' + encodeURIComponent(window.location.href)
);
`
});
});
// Generate two identical routes with new and deprecated URL and issue a warning if route with deprecated URL is ever used.
for (const path of ['/api/security/oidc/callback', '/api/security/v1/oidc']) {
router.get({
path,
validate: {
query: _configSchema.schema.object({
authenticationResponseURI: _configSchema.schema.maybe(_configSchema.schema.uri()),
code: _configSchema.schema.maybe(_configSchema.schema.string()),
error: _configSchema.schema.maybe(_configSchema.schema.string()),
error_description: _configSchema.schema.maybe(_configSchema.schema.string()),
error_uri: _configSchema.schema.maybe(_configSchema.schema.uri()),
iss: _configSchema.schema.maybe(_configSchema.schema.uri({
scheme: ['https']
})),
login_hint: _configSchema.schema.maybe(_configSchema.schema.string()),
target_link_uri: _configSchema.schema.maybe(_configSchema.schema.uri()),
state: _configSchema.schema.maybe(_configSchema.schema.string())
},
// The client MUST ignore unrecognized response parameters according to
// https://openid.net/specs/openid-connect-core-1_0.html#AuthResponseValidation and
// https://tools.ietf.org/html/rfc6749#section-4.1.2.
{
unknowns: 'allow'
})
},
options: {
authRequired: false,
tags: [_tags.ROUTE_TAG_CAN_REDIRECT, _tags.ROUTE_TAG_AUTH_FLOW]
}
}, (0, _licensed_route_handler.createLicensedRouteHandler)(async (context, request, response) => {
const serverBasePath = basePath.serverBasePath;
// An HTTP GET request with a query parameter named `authenticationResponseURI` that includes URL fragment OpenID
// Connect Provider sent during implicit authentication flow to the Kibana own proxy page that extracted that URL
// fragment and put it into `authenticationResponseURI` query string parameter for this endpoint. See more details
// at https://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth
let loginAttempt;
if (request.query.authenticationResponseURI) {
loginAttempt = {
type: _authentication.OIDCLogin.LoginWithImplicitFlow,
authenticationResponseURI: request.query.authenticationResponseURI
};
} else if (request.query.code || request.query.error) {
if (path === '/api/security/v1/oidc') {
logger.warn(`The "${serverBasePath}${path}" URL is deprecated and will stop working in the next major version, please use "${serverBasePath}/api/security/oidc/callback" URL instead.`, {
tags: ['deprecation']
});
}
// An HTTP GET request with a query parameter named `code` (or `error`) as the response to a successful (or
// failed) authentication from an OpenID Connect Provider during authorization code authentication flow.
// See more details at https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth.
loginAttempt = {
type: _authentication.OIDCLogin.LoginWithAuthorizationCodeFlow,
// We pass the path only as we can't be sure of the full URL and Elasticsearch doesn't need it anyway.
authenticationResponseURI: request.url.pathname + request.url.search
};
} else if (request.query.iss) {
logger.warn(`The "${serverBasePath}${path}" URL is deprecated and will stop working in the next major version, please use "${serverBasePath}/api/security/oidc/initiate_login" URL for Third-Party Initiated login instead.`, {
tags: ['deprecation']
});
// An HTTP GET request with a query parameter named `iss` as part of a 3rd party initiated authentication.
// See more details at https://openid.net/specs/openid-connect-core-1_0.html#ThirdPartyInitiatedLogin
loginAttempt = {
type: _authentication.OIDCLogin.LoginInitiatedBy3rdParty,
iss: request.query.iss,
loginHint: request.query.login_hint
};
}
if (!loginAttempt) {
return response.badRequest({
body: 'Unrecognized login attempt.'
});
}
return performOIDCLogin(request, response, loginAttempt);
}));
}
// Generate two identical routes with new and deprecated URL and issue a warning if route with deprecated URL is ever used.
for (const path of ['/api/security/oidc/initiate_login', '/api/security/v1/oidc']) {
/**
* An HTTP POST request with the payload parameter named `iss` as part of a 3rd party initiated authentication.
* See more details at https://openid.net/specs/openid-connect-core-1_0.html#ThirdPartyInitiatedLogin
*/
router.post({
path,
validate: {
body: _configSchema.schema.object({
iss: _configSchema.schema.uri({
scheme: ['https']
}),
login_hint: _configSchema.schema.maybe(_configSchema.schema.string()),
target_link_uri: _configSchema.schema.maybe(_configSchema.schema.uri())
},
// Other parameters MAY be sent, if defined by extensions. Any parameters used that are not understood MUST
// be ignored by the Client according to https://openid.net/specs/openid-connect-core-1_0.html#ThirdPartyInitiatedLogin.
{
unknowns: 'allow'
})
},
options: {
authRequired: false,
xsrfRequired: false,
tags: [_tags.ROUTE_TAG_CAN_REDIRECT, _tags.ROUTE_TAG_AUTH_FLOW]
}
}, (0, _licensed_route_handler.createLicensedRouteHandler)(async (context, request, response) => {
const serverBasePath = basePath.serverBasePath;
if (path === '/api/security/v1/oidc') {
logger.warn(`The "${serverBasePath}${path}" URL is deprecated and will stop working in the next major version, please use "${serverBasePath}/api/security/oidc/initiate_login" URL for Third-Party Initiated login instead.`, {
tags: ['deprecation']
});
}
return performOIDCLogin(request, response, {
type: _authentication.OIDCLogin.LoginInitiatedBy3rdParty,
iss: request.body.iss,
loginHint: request.body.login_hint
});
}));
}
/**
* An HTTP GET request with the query string parameter named `iss` as part of a 3rd party initiated authentication.
* See more details at https://openid.net/specs/openid-connect-core-1_0.html#ThirdPartyInitiatedLogin
*/
router.get({
path: '/api/security/oidc/initiate_login',
validate: {
query: _configSchema.schema.object({
iss: _configSchema.schema.uri({
scheme: ['https']
}),
login_hint: _configSchema.schema.maybe(_configSchema.schema.string()),
target_link_uri: _configSchema.schema.maybe(_configSchema.schema.uri())
},
// Other parameters MAY be sent, if defined by extensions. Any parameters used that are not understood MUST
// be ignored by the Client according to https://openid.net/specs/openid-connect-core-1_0.html#ThirdPartyInitiatedLogin.
{
unknowns: 'allow'
})
},
options: {
authRequired: false,
tags: [_tags.ROUTE_TAG_CAN_REDIRECT, _tags.ROUTE_TAG_AUTH_FLOW]
}
}, (0, _licensed_route_handler.createLicensedRouteHandler)(async (context, request, response) => {
return performOIDCLogin(request, response, {
type: _authentication.OIDCLogin.LoginInitiatedBy3rdParty,
iss: request.query.iss,
loginHint: request.query.login_hint
});
}));
async function performOIDCLogin(request, response, loginAttempt) {
try {
// We handle the fact that the user might get redirected to Kibana while already having a session
// Return an error notifying the user they are already logged in.
const authenticationResult = await getAuthenticationService().login(request, {
provider: {
type: _authentication.OIDCAuthenticationProvider.type
},
value: loginAttempt
});
if (authenticationResult.succeeded()) {
return response.forbidden({
body: _i18n.i18n.translate('xpack.security.conflictingSessionError', {
defaultMessage: 'Sorry, you already have an active Kibana session. ' + 'If you want to start a new one, please logout from the existing session first.'
})
});
}
if (authenticationResult.redirected()) {
return response.redirected({
headers: {
location: authenticationResult.redirectURL
}
});
}
return response.unauthorized({
body: authenticationResult.error
});
} catch (error) {
return response.customError((0, _errors.wrapIntoCustomErrorResponse)(error));
}
}
}