mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
Removes deprecated platform security v1 routes (#203915)
## Summary Removes the v1 routes deprecated in https://github.com/elastic/kibana/pull/199656 Part of Kibana 9.0.0 readiness https://github.com/elastic/kibana-team/issues/1190
This commit is contained in:
parent
95094b21e5
commit
9f4e851272
8 changed files with 285 additions and 435 deletions
|
@ -194,9 +194,7 @@ export class AuthenticationService {
|
|||
}
|
||||
|
||||
const isAuthRoute = request.route.options.tags.includes(ROUTE_TAG_AUTH_FLOW);
|
||||
const isLogoutRoute =
|
||||
request.route.path === '/api/security/logout' ||
|
||||
request.route.path === '/api/v1/security/logout';
|
||||
const isLogoutRoute = request.route.path === '/api/security/logout';
|
||||
|
||||
// If users can eventually re-login we want to redirect them directly to the page they tried
|
||||
// to access initially, but we only want to do that for routes that aren't part of the various
|
||||
|
|
|
@ -788,7 +788,7 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
method: 'POST',
|
||||
path: '/_security/saml/prepare',
|
||||
body: {
|
||||
acs: 'test-protocol://test-hostname:1234/mock-server-basepath/api/security/v1/saml',
|
||||
acs: 'test-protocol://test-hostname:1234/mock-server-basepath/api/security/saml/callback',
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -830,7 +830,7 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
method: 'POST',
|
||||
path: '/_security/saml/prepare',
|
||||
body: {
|
||||
acs: 'test-protocol://test-hostname:1234/mock-server-basepath/api/security/v1/saml',
|
||||
acs: 'test-protocol://test-hostname:1234/mock-server-basepath/api/security/saml/callback',
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -900,7 +900,7 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
method: 'POST',
|
||||
path: '/_security/saml/prepare',
|
||||
body: {
|
||||
acs: 'test-protocol://test-hostname:1234/mock-server-basepath/api/security/v1/saml',
|
||||
acs: 'test-protocol://test-hostname:1234/mock-server-basepath/api/security/saml/callback',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -1003,7 +1003,7 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
method: 'POST',
|
||||
path: '/_security/saml/prepare',
|
||||
body: {
|
||||
acs: 'test-protocol://test-hostname:1234/mock-server-basepath/api/security/v1/saml',
|
||||
acs: 'test-protocol://test-hostname:1234/mock-server-basepath/api/security/saml/callback',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -1294,7 +1294,7 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
path: '/_security/saml/invalidate',
|
||||
body: {
|
||||
query_string: 'SAMLRequest=xxx%20yyy',
|
||||
acs: 'test-protocol://test-hostname:1234/mock-server-basepath/api/security/v1/saml',
|
||||
acs: 'test-protocol://test-hostname:1234/mock-server-basepath/api/security/saml/callback',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -1408,7 +1408,7 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
path: '/_security/saml/invalidate',
|
||||
body: {
|
||||
query_string: 'SAMLRequest=xxx%20yyy',
|
||||
acs: 'test-protocol://test-hostname:1234/mock-server-basepath/api/security/v1/saml',
|
||||
acs: 'test-protocol://test-hostname:1234/mock-server-basepath/api/security/saml/callback',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -1430,7 +1430,7 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
path: '/_security/saml/invalidate',
|
||||
body: {
|
||||
query_string: 'SAMLRequest=xxx%20yyy',
|
||||
acs: 'test-protocol://test-hostname:1234/mock-server-basepath/api/security/v1/saml',
|
||||
acs: 'test-protocol://test-hostname:1234/mock-server-basepath/api/security/saml/callback',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -659,7 +659,7 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider {
|
|||
private getACS() {
|
||||
return `${this.options.getServerBaseURL()}${
|
||||
this.options.basePath.serverBasePath
|
||||
}/api/security/v1/saml`;
|
||||
}/api/security/saml/callback`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -78,6 +78,16 @@ describe('Common authentication routes', () => {
|
|||
query: expect.any(Type),
|
||||
params: undefined,
|
||||
});
|
||||
expect(routeConfig.security).toEqual({
|
||||
authz: {
|
||||
enabled: false,
|
||||
reason: 'This route must remain accessible to 3rd-party IdPs',
|
||||
},
|
||||
authc: {
|
||||
enabled: false,
|
||||
reason: 'This route must remain accessible to 3rd-party IdPs',
|
||||
},
|
||||
});
|
||||
|
||||
const queryValidator = (routeConfig.validate as any).query as Type<any>;
|
||||
expect(queryValidator.validate({ someRandomField: 'some-random' })).toEqual({
|
||||
|
@ -211,6 +221,17 @@ describe('Common authentication routes', () => {
|
|||
query: undefined,
|
||||
params: undefined,
|
||||
});
|
||||
expect(routeConfig.security).toEqual({
|
||||
authz: {
|
||||
enabled: false,
|
||||
reason: `This route provides basic and token login capability, which is delegated to the internal authentication service`,
|
||||
},
|
||||
authc: {
|
||||
enabled: false,
|
||||
reason:
|
||||
'This route is used for authentication - it does not require existing authentication',
|
||||
},
|
||||
});
|
||||
|
||||
const bodyValidator = (routeConfig.validate as any).body as Type<any>;
|
||||
expect(
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import type { TypeOf } from '@kbn/config-schema';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { parseNextURL } from '@kbn/std';
|
||||
|
||||
import type { RouteDefinitionParams } from '..';
|
||||
|
@ -34,130 +33,71 @@ export function defineCommonRoutes({
|
|||
license,
|
||||
logger,
|
||||
buildFlavor,
|
||||
docLinks,
|
||||
}: RouteDefinitionParams) {
|
||||
// Generate two identical routes with new and deprecated URL and issue a warning if route with deprecated URL is ever used.
|
||||
// For a serverless build, do not register deprecated versioned routes
|
||||
for (const path of [
|
||||
'/api/security/logout',
|
||||
...(buildFlavor !== 'serverless' ? ['/api/security/v1/logout'] : []),
|
||||
]) {
|
||||
const isDeprecated = path === '/api/security/v1/logout';
|
||||
router.get(
|
||||
{
|
||||
path,
|
||||
security: {
|
||||
authz: {
|
||||
enabled: false,
|
||||
reason: 'This route must remain accessible to 3rd-party IdPs',
|
||||
},
|
||||
authc: {
|
||||
enabled: false,
|
||||
reason:
|
||||
'This route is used for authentication - it does not require existing authentication',
|
||||
},
|
||||
router.get(
|
||||
{
|
||||
path: '/api/security/logout',
|
||||
security: {
|
||||
authz: {
|
||||
enabled: false,
|
||||
reason: 'This route must remain accessible to 3rd-party IdPs',
|
||||
},
|
||||
// Allow unknown query parameters as this endpoint can be hit by the 3rd-party with any
|
||||
// set of query string parameters (e.g. SAML/OIDC logout request/response parameters).
|
||||
validate: { query: schema.object({}, { unknowns: 'allow' }) },
|
||||
options: {
|
||||
access: 'public',
|
||||
excludeFromOAS: true,
|
||||
tags: [ROUTE_TAG_CAN_REDIRECT, ROUTE_TAG_AUTH_FLOW],
|
||||
...(isDeprecated && {
|
||||
deprecated: {
|
||||
documentationUrl: docLinks.links.security.deprecatedV1Endpoints,
|
||||
severity: 'warning',
|
||||
message: i18n.translate('xpack.security.deprecations.logoutRouteMessage', {
|
||||
defaultMessage:
|
||||
'The "{path}" URL is deprecated and will be removed in the next major version. Use "/api/security/logout" instead.',
|
||||
values: { path },
|
||||
}),
|
||||
reason: {
|
||||
type: 'migrate',
|
||||
newApiMethod: 'GET',
|
||||
newApiPath: '/api/security/logout',
|
||||
},
|
||||
},
|
||||
}),
|
||||
authc: {
|
||||
enabled: false,
|
||||
reason: 'This route must remain accessible to 3rd-party IdPs',
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const serverBasePath = basePath.serverBasePath;
|
||||
if (isDeprecated) {
|
||||
logger.warn(
|
||||
`The "${serverBasePath}${path}" URL is deprecated and will stop working in the next major version. Use "${serverBasePath}/api/security/logout" URL instead.`,
|
||||
{ tags: ['deprecation'] }
|
||||
);
|
||||
}
|
||||
|
||||
if (!canRedirectRequest(request)) {
|
||||
return response.badRequest({
|
||||
body: 'Client should be able to process redirect response.',
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const deauthenticationResult = await getAuthenticationService().logout(request);
|
||||
if (deauthenticationResult.failed()) {
|
||||
return response.customError(wrapIntoCustomErrorResponse(deauthenticationResult.error));
|
||||
}
|
||||
|
||||
return response.redirected({
|
||||
headers: { location: deauthenticationResult.redirectURL || `${serverBasePath}/` },
|
||||
});
|
||||
} catch (error) {
|
||||
return response.customError(wrapIntoCustomErrorResponse(error));
|
||||
}
|
||||
// Allow unknown query parameters as this endpoint can be hit by the 3rd-party with any
|
||||
// set of query string parameters (e.g. SAML/OIDC logout request/response parameters).
|
||||
validate: { query: schema.object({}, { unknowns: 'allow' }) },
|
||||
options: {
|
||||
access: 'public',
|
||||
excludeFromOAS: true,
|
||||
tags: [ROUTE_TAG_CAN_REDIRECT, ROUTE_TAG_AUTH_FLOW],
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const serverBasePath = basePath.serverBasePath;
|
||||
if (!canRedirectRequest(request)) {
|
||||
return response.badRequest({
|
||||
body: 'Client should be able to process redirect response.',
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Generate two identical routes with new and deprecated URL and issue a warning if route with deprecated URL is ever used.
|
||||
// For a serverless build, do not register deprecated versioned routes
|
||||
for (const path of [
|
||||
'/internal/security/me',
|
||||
...(buildFlavor !== 'serverless' ? ['/api/security/v1/me'] : []),
|
||||
]) {
|
||||
const isDeprecated = path === '/api/security/v1/me';
|
||||
router.get(
|
||||
{
|
||||
path,
|
||||
security: {
|
||||
authz: {
|
||||
enabled: false,
|
||||
reason: `This route delegates authorization to Core's security service; there must be an authenticated user for this route to return information`,
|
||||
},
|
||||
},
|
||||
validate: false,
|
||||
options: {
|
||||
access: isDeprecated ? 'public' : 'internal',
|
||||
...(isDeprecated && {
|
||||
deprecated: {
|
||||
documentationUrl: docLinks.links.security.deprecatedV1Endpoints,
|
||||
severity: 'warning',
|
||||
message: i18n.translate('xpack.security.deprecations.meRouteMessage', {
|
||||
defaultMessage:
|
||||
'The "{path}" endpoint is deprecated and will be removed in the next major version.',
|
||||
values: { path },
|
||||
}),
|
||||
reason: { type: 'remove' },
|
||||
},
|
||||
}),
|
||||
try {
|
||||
const deauthenticationResult = await getAuthenticationService().logout(request);
|
||||
if (deauthenticationResult.failed()) {
|
||||
return response.customError(wrapIntoCustomErrorResponse(deauthenticationResult.error));
|
||||
}
|
||||
|
||||
return response.redirected({
|
||||
headers: { location: deauthenticationResult.redirectURL || `${serverBasePath}/` },
|
||||
});
|
||||
} catch (error) {
|
||||
return response.customError(wrapIntoCustomErrorResponse(error));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
{
|
||||
path: '/internal/security/me',
|
||||
security: {
|
||||
authz: {
|
||||
enabled: false,
|
||||
reason: `This route delegates authorization to Core's security service; there must be an authenticated user for this route to return information`,
|
||||
},
|
||||
},
|
||||
createLicensedRouteHandler(async (context, request, response) => {
|
||||
if (isDeprecated) {
|
||||
logger.warn(
|
||||
`The "${basePath.serverBasePath}${path}" endpoint is deprecated and will be removed in the next major version.`,
|
||||
{ tags: ['deprecation'] }
|
||||
);
|
||||
}
|
||||
const { security: coreSecurity } = await context.core;
|
||||
return response.ok({ body: coreSecurity.authc.getCurrentUser()! });
|
||||
})
|
||||
);
|
||||
}
|
||||
validate: false,
|
||||
options: {
|
||||
access: 'internal',
|
||||
},
|
||||
},
|
||||
createLicensedRouteHandler(async (context, request, response) => {
|
||||
const { security: coreSecurity } = await context.core;
|
||||
return response.ok({ body: coreSecurity.authc.getCurrentUser()! });
|
||||
})
|
||||
);
|
||||
|
||||
const basicParamsSchema = schema.object({
|
||||
username: schema.string({ minLength: 1 }),
|
||||
|
|
|
@ -22,63 +22,44 @@ import { ROUTE_TAG_AUTH_FLOW, ROUTE_TAG_CAN_REDIRECT } from '../tags';
|
|||
export function defineOIDCRoutes({
|
||||
router,
|
||||
httpResources,
|
||||
logger,
|
||||
getAuthenticationService,
|
||||
basePath,
|
||||
docLinks,
|
||||
}: RouteDefinitionParams) {
|
||||
// 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']) {
|
||||
const isDeprecated = path === '/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,
|
||||
excludeFromOAS: true,
|
||||
...(isDeprecated && {
|
||||
deprecated: {
|
||||
documentationUrl: docLinks.links.security.deprecatedV1Endpoints,
|
||||
severity: 'warning',
|
||||
message: i18n.translate('xpack.security.deprecations.oidcImplicitRouteMessage', {
|
||||
defaultMessage:
|
||||
'The "{path}" URL is deprecated and will be removed in the next major version. Use "/api/security/oidc/implicit" instead.',
|
||||
values: { path },
|
||||
}),
|
||||
reason: {
|
||||
type: 'migrate',
|
||||
newApiMethod: 'GET',
|
||||
newApiPath: '/api/security/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: '/api/security/oidc/implicit',
|
||||
validate: false,
|
||||
security: {
|
||||
authz: {
|
||||
enabled: false,
|
||||
reason: 'This route must remain accessible to 3rd-party OIDC providers',
|
||||
},
|
||||
authc: {
|
||||
enabled: false,
|
||||
reason: 'This route must remain accessible to 3rd-party OIDC providers',
|
||||
},
|
||||
},
|
||||
(context, request, response) => {
|
||||
const serverBasePath = basePath.serverBasePath;
|
||||
if (isDeprecated) {
|
||||
logger.warn(
|
||||
`The "${serverBasePath}${path}" URL is deprecated and will stop working in the next major version. Use "${serverBasePath}/api/security/oidc/implicit" URL instead.`,
|
||||
{ tags: ['deprecation'] }
|
||||
);
|
||||
}
|
||||
return response.renderHtml({
|
||||
body: `
|
||||
options: {
|
||||
excludeFromOAS: true,
|
||||
},
|
||||
},
|
||||
(context, request, response) => {
|
||||
const serverBasePath = basePath.serverBasePath;
|
||||
return response.renderHtml({
|
||||
body: `
|
||||
<!DOCTYPE html>
|
||||
<title>Kibana OpenID Connect Login</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<script src="${serverBasePath}/internal/security/oidc/implicit.js"></script>
|
||||
`,
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* The route that accompanies `/api/security/oidc/implicit` and renders a JavaScript snippet
|
||||
|
@ -89,7 +70,17 @@ export function defineOIDCRoutes({
|
|||
{
|
||||
path: '/internal/security/oidc/implicit.js',
|
||||
validate: false,
|
||||
options: { authRequired: false, excludeFromOAS: true },
|
||||
security: {
|
||||
authz: {
|
||||
enabled: false,
|
||||
reason: 'This route must remain accessible to 3rd-party OIDC providers',
|
||||
},
|
||||
authc: {
|
||||
enabled: false,
|
||||
reason: 'This route must remain accessible to 3rd-party OIDC providers',
|
||||
},
|
||||
},
|
||||
options: { excludeFromOAS: true },
|
||||
},
|
||||
(context, request, response) => {
|
||||
const serverBasePath = basePath.serverBasePath;
|
||||
|
@ -103,183 +94,117 @@ export function defineOIDCRoutes({
|
|||
}
|
||||
);
|
||||
|
||||
// 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']) {
|
||||
const isDeprecated = path === '/api/security/v1/oidc';
|
||||
router.get(
|
||||
{
|
||||
path,
|
||||
security: {
|
||||
authz: {
|
||||
enabled: false,
|
||||
reason: 'This route must remain accessible to 3rd-party OIDC providers',
|
||||
},
|
||||
router.get(
|
||||
{
|
||||
path: '/api/security/oidc/callback',
|
||||
security: {
|
||||
authz: {
|
||||
enabled: false,
|
||||
reason: 'This route must remain accessible to 3rd-party OIDC providers',
|
||||
},
|
||||
validate: {
|
||||
query: schema.object(
|
||||
{
|
||||
authenticationResponseURI: schema.maybe(schema.uri()),
|
||||
code: schema.maybe(schema.string()),
|
||||
error: schema.maybe(schema.string()),
|
||||
error_description: schema.maybe(schema.string()),
|
||||
error_uri: schema.maybe(schema.uri()),
|
||||
iss: schema.maybe(schema.uri({ scheme: ['https'] })),
|
||||
login_hint: schema.maybe(schema.string()),
|
||||
target_link_uri: schema.maybe(schema.uri()),
|
||||
state: schema.maybe(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: {
|
||||
access: 'public',
|
||||
excludeFromOAS: true,
|
||||
authRequired: false,
|
||||
tags: [ROUTE_TAG_CAN_REDIRECT, ROUTE_TAG_AUTH_FLOW],
|
||||
...(isDeprecated && {
|
||||
deprecated: {
|
||||
documentationUrl: docLinks.links.security.deprecatedV1Endpoints,
|
||||
severity: 'warning',
|
||||
message: i18n.translate('xpack.security.deprecations.oidcCallbackRouteMessage', {
|
||||
defaultMessage:
|
||||
'The "{path}" URL is deprecated and will be removed in the next major version. Use "/api/security/oidc/callback" instead.',
|
||||
values: { path },
|
||||
}),
|
||||
reason: {
|
||||
type: 'migrate',
|
||||
newApiMethod: 'GET',
|
||||
newApiPath: '/api/security/oidc/callback',
|
||||
},
|
||||
},
|
||||
}),
|
||||
authc: {
|
||||
enabled: false,
|
||||
reason: 'This route must remain accessible to 3rd-party OIDC providers',
|
||||
},
|
||||
},
|
||||
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: ProviderLoginAttempt | undefined;
|
||||
if (request.query.authenticationResponseURI) {
|
||||
loginAttempt = {
|
||||
type: OIDCLogin.LoginWithImplicitFlow,
|
||||
authenticationResponseURI: request.query.authenticationResponseURI,
|
||||
};
|
||||
} else if (request.query.code || request.query.error) {
|
||||
if (isDeprecated) {
|
||||
logger.warn(
|
||||
`The "${serverBasePath}${path}" URL is deprecated and will stop working in the next major version. 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: 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. 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: 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']) {
|
||||
const isDeprecated = path === '/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,
|
||||
security: {
|
||||
authz: {
|
||||
enabled: false,
|
||||
reason: 'This route must remain accessible to 3rd-party OIDC providers',
|
||||
validate: {
|
||||
query: schema.object(
|
||||
{
|
||||
authenticationResponseURI: schema.maybe(schema.uri()),
|
||||
code: schema.maybe(schema.string()),
|
||||
error: schema.maybe(schema.string()),
|
||||
error_description: schema.maybe(schema.string()),
|
||||
error_uri: schema.maybe(schema.uri()),
|
||||
state: schema.maybe(schema.string()),
|
||||
},
|
||||
},
|
||||
validate: {
|
||||
body: schema.object(
|
||||
{
|
||||
iss: schema.uri({ scheme: ['https'] }),
|
||||
login_hint: schema.maybe(schema.string()),
|
||||
target_link_uri: schema.maybe(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: {
|
||||
access: 'public',
|
||||
excludeFromOAS: true,
|
||||
authRequired: false,
|
||||
xsrfRequired: false,
|
||||
tags: [ROUTE_TAG_CAN_REDIRECT, ROUTE_TAG_AUTH_FLOW],
|
||||
...(isDeprecated && {
|
||||
deprecated: {
|
||||
documentationUrl: docLinks.links.security.deprecatedV1Endpoints,
|
||||
severity: 'warning',
|
||||
message: i18n.translate('xpack.security.deprecations.oidcInitiateRouteMessage', {
|
||||
defaultMessage:
|
||||
'The "{path}" URL is deprecated and will be removed in the next major version. Use "/api/security/oidc/initiate_login" instead.',
|
||||
values: { path },
|
||||
}),
|
||||
reason: {
|
||||
type: 'migrate',
|
||||
newApiMethod: 'POST',
|
||||
newApiPath: '/api/security/oidc/initiate_login',
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
// 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' }
|
||||
),
|
||||
},
|
||||
createLicensedRouteHandler(async (context, request, response) => {
|
||||
const serverBasePath = basePath.serverBasePath;
|
||||
if (isDeprecated) {
|
||||
logger.warn(
|
||||
`The "${serverBasePath}${path}" URL is deprecated and will stop working in the next major version. Use "${serverBasePath}/api/security/oidc/initiate_login" URL for Third-Party Initiated login instead.`,
|
||||
{ tags: ['deprecation'] }
|
||||
);
|
||||
}
|
||||
options: {
|
||||
access: 'public',
|
||||
excludeFromOAS: true,
|
||||
tags: [ROUTE_TAG_CAN_REDIRECT, ROUTE_TAG_AUTH_FLOW],
|
||||
},
|
||||
},
|
||||
createLicensedRouteHandler(async (context, request, response) => {
|
||||
// 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: ProviderLoginAttempt | undefined;
|
||||
if (request.query.authenticationResponseURI) {
|
||||
loginAttempt = {
|
||||
type: OIDCLogin.LoginWithImplicitFlow,
|
||||
authenticationResponseURI: request.query.authenticationResponseURI,
|
||||
};
|
||||
} else if (request.query.code || request.query.error) {
|
||||
// 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: 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,
|
||||
};
|
||||
}
|
||||
|
||||
return performOIDCLogin(request, response, {
|
||||
type: OIDCLogin.LoginInitiatedBy3rdParty,
|
||||
iss: request.body.iss,
|
||||
loginHint: request.body.login_hint,
|
||||
if (!loginAttempt) {
|
||||
return response.badRequest({
|
||||
body: 'Unrecognized login attempt.',
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return performOIDCLogin(request, response, loginAttempt);
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* 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: '/api/security/oidc/initiate_login',
|
||||
security: {
|
||||
authz: {
|
||||
enabled: false,
|
||||
reason: 'This route must remain accessible to 3rd-party OIDC providers',
|
||||
},
|
||||
authc: {
|
||||
enabled: false,
|
||||
reason: 'This route must remain accessible to 3rd-party OIDC providers',
|
||||
},
|
||||
},
|
||||
validate: {
|
||||
body: schema.object(
|
||||
{
|
||||
iss: schema.uri({ scheme: ['https'] }),
|
||||
login_hint: schema.maybe(schema.string()),
|
||||
target_link_uri: schema.maybe(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: {
|
||||
access: 'public',
|
||||
excludeFromOAS: true,
|
||||
xsrfRequired: false,
|
||||
tags: [ROUTE_TAG_CAN_REDIRECT, ROUTE_TAG_AUTH_FLOW],
|
||||
},
|
||||
},
|
||||
createLicensedRouteHandler(async (context, request, response) => {
|
||||
return performOIDCLogin(request, response, {
|
||||
type: 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.
|
||||
|
@ -293,6 +218,10 @@ export function defineOIDCRoutes({
|
|||
enabled: false,
|
||||
reason: 'This route must remain accessible to 3rd-party OIDC providers',
|
||||
},
|
||||
authc: {
|
||||
enabled: false,
|
||||
reason: 'This route must remain accessible to 3rd-party OIDC providers',
|
||||
},
|
||||
},
|
||||
validate: {
|
||||
query: schema.object(
|
||||
|
@ -309,7 +238,6 @@ export function defineOIDCRoutes({
|
|||
options: {
|
||||
access: 'public',
|
||||
excludeFromOAS: true,
|
||||
authRequired: false,
|
||||
tags: [ROUTE_TAG_CAN_REDIRECT, ROUTE_TAG_AUTH_FLOW],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -43,19 +43,9 @@ describe('SAML authentication routes', () => {
|
|||
routeHandler = acsRouteHandler;
|
||||
});
|
||||
|
||||
it('additionally registers BWC route', () => {
|
||||
expect(
|
||||
router.post.mock.calls.find(([{ path }]) => path === '/api/security/saml/callback')
|
||||
).toBeDefined();
|
||||
expect(
|
||||
router.post.mock.calls.find(([{ path }]) => path === '/api/security/v1/saml')
|
||||
).toBeDefined();
|
||||
});
|
||||
|
||||
it('correctly defines route.', () => {
|
||||
expect(routeConfig.options).toEqual({
|
||||
access: 'public',
|
||||
authRequired: false,
|
||||
excludeFromOAS: true,
|
||||
xsrfRequired: false,
|
||||
tags: [ROUTE_TAG_CAN_REDIRECT, ROUTE_TAG_AUTH_FLOW],
|
||||
|
@ -65,6 +55,16 @@ describe('SAML authentication routes', () => {
|
|||
query: undefined,
|
||||
params: undefined,
|
||||
});
|
||||
expect(routeConfig.security).toEqual({
|
||||
authz: {
|
||||
enabled: false,
|
||||
reason: 'This route must remain accessible to 3rd-party SAML providers',
|
||||
},
|
||||
authc: {
|
||||
enabled: false,
|
||||
reason: 'This route must remain accessible to 3rd-party SAML providers',
|
||||
},
|
||||
});
|
||||
|
||||
const bodyValidator = (routeConfig.validate as any).body as Type<any>;
|
||||
expect(bodyValidator.validate({ SAMLResponse: 'saml-response' })).toEqual({
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import type { RouteDefinitionParams } from '..';
|
||||
import { SAMLAuthenticationProvider, SAMLLogin } from '../../authentication';
|
||||
|
@ -15,87 +14,51 @@ import { ROUTE_TAG_AUTH_FLOW, ROUTE_TAG_CAN_REDIRECT } from '../tags';
|
|||
/**
|
||||
* Defines routes required for SAML authentication.
|
||||
*/
|
||||
export function defineSAMLRoutes({
|
||||
router,
|
||||
getAuthenticationService,
|
||||
basePath,
|
||||
logger,
|
||||
buildFlavor,
|
||||
docLinks,
|
||||
}: RouteDefinitionParams) {
|
||||
// Generate two identical routes with new and deprecated URL and issue a warning if route with deprecated URL is ever used.
|
||||
// For a serverless build, do not register deprecated versioned routes
|
||||
for (const path of [
|
||||
'/api/security/saml/callback',
|
||||
...(buildFlavor !== 'serverless' ? ['/api/security/v1/saml'] : []),
|
||||
]) {
|
||||
const isDeprecated = path === '/api/security/v1/saml';
|
||||
router.post(
|
||||
{
|
||||
path,
|
||||
security: {
|
||||
authz: {
|
||||
enabled: false,
|
||||
reason: 'This route must remain accessible to 3rd-party SAML providers',
|
||||
},
|
||||
export function defineSAMLRoutes({ router, getAuthenticationService }: RouteDefinitionParams) {
|
||||
router.post(
|
||||
{
|
||||
path: '/api/security/saml/callback',
|
||||
security: {
|
||||
authz: {
|
||||
enabled: false,
|
||||
reason: 'This route must remain accessible to 3rd-party SAML providers',
|
||||
},
|
||||
validate: {
|
||||
body: schema.object(
|
||||
{ SAMLResponse: schema.string(), RelayState: schema.maybe(schema.string()) },
|
||||
{ unknowns: 'ignore' }
|
||||
),
|
||||
},
|
||||
options: {
|
||||
access: 'public',
|
||||
excludeFromOAS: true,
|
||||
authRequired: false,
|
||||
xsrfRequired: false,
|
||||
tags: [ROUTE_TAG_CAN_REDIRECT, ROUTE_TAG_AUTH_FLOW],
|
||||
...(isDeprecated && {
|
||||
deprecated: {
|
||||
documentationUrl: docLinks.links.security.deprecatedV1Endpoints,
|
||||
severity: 'warning',
|
||||
message: i18n.translate('xpack.security.deprecations.samlPostRouteMessage', {
|
||||
defaultMessage:
|
||||
'The "{path}" URL is deprecated and will be removed in the next major version. Use "/api/security/saml/callback" instead.',
|
||||
values: { path },
|
||||
}),
|
||||
reason: {
|
||||
type: 'migrate',
|
||||
newApiMethod: 'POST',
|
||||
newApiPath: '/api/security/saml/callback',
|
||||
},
|
||||
},
|
||||
}),
|
||||
authc: {
|
||||
enabled: false,
|
||||
reason: 'This route must remain accessible to 3rd-party SAML providers',
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
if (isDeprecated) {
|
||||
const serverBasePath = basePath.serverBasePath;
|
||||
logger.warn(
|
||||
// When authenticating using SAML we _expect_ to redirect to the SAML Identity provider.
|
||||
`The "${serverBasePath}${path}" URL is deprecated and might stop working in a future release. Use "${serverBasePath}/api/security/saml/callback" URL instead.`
|
||||
);
|
||||
}
|
||||
validate: {
|
||||
body: schema.object(
|
||||
{ SAMLResponse: schema.string(), RelayState: schema.maybe(schema.string()) },
|
||||
{ unknowns: 'ignore' }
|
||||
),
|
||||
},
|
||||
options: {
|
||||
access: 'public',
|
||||
excludeFromOAS: true,
|
||||
xsrfRequired: false,
|
||||
tags: [ROUTE_TAG_CAN_REDIRECT, ROUTE_TAG_AUTH_FLOW],
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
// When authenticating using SAML we _expect_ to redirect to the Kibana target location.
|
||||
const authenticationResult = await getAuthenticationService().login(request, {
|
||||
provider: { type: SAMLAuthenticationProvider.type },
|
||||
value: {
|
||||
type: SAMLLogin.LoginWithSAMLResponse,
|
||||
samlResponse: request.body.SAMLResponse,
|
||||
relayState: request.body.RelayState,
|
||||
},
|
||||
});
|
||||
|
||||
// When authenticating using SAML we _expect_ to redirect to the Kibana target location.
|
||||
const authenticationResult = await getAuthenticationService().login(request, {
|
||||
provider: { type: SAMLAuthenticationProvider.type },
|
||||
value: {
|
||||
type: SAMLLogin.LoginWithSAMLResponse,
|
||||
samlResponse: request.body.SAMLResponse,
|
||||
relayState: request.body.RelayState,
|
||||
},
|
||||
if (authenticationResult.redirected()) {
|
||||
return response.redirected({
|
||||
headers: { location: authenticationResult.redirectURL! },
|
||||
});
|
||||
|
||||
if (authenticationResult.redirected()) {
|
||||
return response.redirected({
|
||||
headers: { location: authenticationResult.redirectURL! },
|
||||
});
|
||||
}
|
||||
|
||||
return response.unauthorized({ body: authenticationResult.error });
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return response.unauthorized({ body: authenticationResult.error });
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue