diff --git a/x-pack/plugins/security/server/authentication/authentication_service.test.ts b/x-pack/plugins/security/server/authentication/authentication_service.test.ts index ea95f308eb04..d29c780e414f 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.test.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.test.ts @@ -128,6 +128,19 @@ describe('AuthenticationService', () => { expect.any(Function) ); }); + + it('properly registers auth handler with no providers', () => { + mockSetupAuthenticationParams.config.authc = { + ...mockSetupAuthenticationParams.config.authc, + sortedProviders: [], + }; + service.setup(mockSetupAuthenticationParams); + + expect(mockSetupAuthenticationParams.http.registerAuth).toHaveBeenCalledTimes(1); + expect(mockSetupAuthenticationParams.http.registerAuth).toHaveBeenCalledWith( + expect.any(Function) + ); + }); }); describe('#start()', () => { diff --git a/x-pack/plugins/security/server/authentication/authentication_service.ts b/x-pack/plugins/security/server/authentication/authentication_service.ts index ac66e62e97c8..d05260094df8 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.ts @@ -98,7 +98,8 @@ export class AuthenticationService { // 2. Login selector is disabled, but the provider with the lowest `order` uses login form const isLoginPageAvailable = config.authc.selector.enabled || - shouldProviderUseLoginForm(config.authc.sortedProviders[0].type); + (config.authc.sortedProviders.length > 0 && + shouldProviderUseLoginForm(config.authc.sortedProviders[0].type)); http.registerAuth(async (request, response, t) => { if (!license.isLicenseAvailable()) { diff --git a/x-pack/plugins/security/server/authentication/authenticator.test.ts b/x-pack/plugins/security/server/authentication/authenticator.test.ts index cc232298bc87..c95944be75f9 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.test.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.test.ts @@ -1872,6 +1872,24 @@ describe('Authenticator', () => { expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); }); + it('if session does not exist and providers is empty, redirects to default logout path.', async () => { + const request = httpServerMock.createKibanaRequest(); + + mockOptions = getMockOptions({ + providers: { basic: { basic1: { order: 0, enabled: false } } }, + }); + authenticator = new Authenticator(mockOptions); + + await expect(authenticator.logout(request)).resolves.toEqual( + DeauthenticationResult.redirectTo( + '/mock-server-basepath/security/logged_out?msg=LOGGED_OUT' + ) + ); + + expect(mockBasicAuthenticationProvider.logout).not.toHaveBeenCalled(); + expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); + }); + it('redirects to login form if session does not exist and provider name is invalid', async () => { const request = httpServerMock.createKibanaRequest({ query: { provider: 'foo' } }); mockOptions.session.get.mockResolvedValue(null); diff --git a/x-pack/plugins/security/server/authentication/authenticator.ts b/x-pack/plugins/security/server/authentication/authenticator.ts index 5608a3558033..ec804668c607 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.ts @@ -806,10 +806,7 @@ export class Authenticator { * @param providerType Type of the provider that handles logout. If not specified, then the first * provider in the chain (default) is assumed. */ - private getLoggedOutURL( - request: KibanaRequest, - providerType: string = this.options.config.authc.sortedProviders[0].type - ) { + private getLoggedOutURL(request: KibanaRequest, providerType?: string) { // The app that handles logout needs to know the reason of the logout and the URL we may need to // redirect user to once they log in again (e.g. when session expires). const searchParams = new URLSearchParams(); @@ -825,7 +822,12 @@ export class Authenticator { // Query string may contain the path where logout has been called or // logout reason that login page may need to know. - return this.options.config.authc.selector.enabled || shouldProviderUseLoginForm(providerType) + return this.options.config.authc.selector.enabled || + (providerType + ? shouldProviderUseLoginForm(providerType) + : this.options.config.authc.sortedProviders.length > 0 + ? shouldProviderUseLoginForm(this.options.config.authc.sortedProviders[0].type) + : false) ? `${this.options.basePath.serverBasePath}/login?${searchParams.toString()}` : `${this.options.basePath.serverBasePath}/security/logged_out?${searchParams.toString()}`; } diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js index 1d0be47cb810..f95237a76a86 100644 --- a/x-pack/scripts/functional_tests.js +++ b/x-pack/scripts/functional_tests.js @@ -53,6 +53,7 @@ const onlyNotInCoverageTests = [ require.resolve('../test/security_api_integration/login_selector.config.ts'), require.resolve('../test/security_api_integration/audit.config.ts'), require.resolve('../test/security_api_integration/http_bearer.config.ts'), + require.resolve('../test/security_api_integration/http_no_auth_providers.config.ts'), require.resolve('../test/security_api_integration/kerberos.config.ts'), require.resolve('../test/security_api_integration/kerberos_anonymous_access.config.ts'), require.resolve('../test/security_api_integration/pki.config.ts'), diff --git a/x-pack/test/security_api_integration/http_no_auth_providers.config.ts b/x-pack/test/security_api_integration/http_no_auth_providers.config.ts new file mode 100644 index 000000000000..b46bbe3134dd --- /dev/null +++ b/x-pack/test/security_api_integration/http_no_auth_providers.config.ts @@ -0,0 +1,36 @@ +/* + * 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. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; +import { services } from './services'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts')); + + return { + testFiles: [require.resolve('./tests/http_no_auth_providers')], + servers: xPackAPITestsConfig.get('servers'), + security: { disableTestUser: true }, + services, + junit: { + reportName: 'X-Pack Security API Integration Tests (HTTP without providers)', + }, + + esTestCluster: xPackAPITestsConfig.get('esTestCluster'), + + kbnTestServer: { + ...xPackAPITestsConfig.get('kbnTestServer'), + serverArgs: [ + ...xPackAPITestsConfig.get('kbnTestServer.serverArgs'), + `--xpack.security.authc.http.schemes=${JSON.stringify(['apikey', 'basic'])}`, + `--xpack.security.authc.providers=${JSON.stringify({ + basic: { basic1: { order: 0, enabled: false } }, + })}`, + ], + }, + }; +} diff --git a/x-pack/test/security_api_integration/tests/http_no_auth_providers/authentication.ts b/x-pack/test/security_api_integration/tests/http_no_auth_providers/authentication.ts new file mode 100644 index 000000000000..42951f8c3fed --- /dev/null +++ b/x-pack/test/security_api_integration/tests/http_no_auth_providers/authentication.ts @@ -0,0 +1,39 @@ +/* + * 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. + */ + +import expect from '@kbn/expect'; +import { adminTestUser } from '@kbn/test'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertestWithoutAuth'); + + describe('authorization', () => { + it('fail request without authorization request header', async () => { + await supertest.get('/internal/security/me').set('kbn-xsrf', 'true').expect(401); + }); + + it('accept request with authorization request header', async () => { + const credentials = Buffer.from( + `${adminTestUser.username}:${adminTestUser.password}` + ).toString('base64'); + + const { body: user, headers } = await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'xxx') + .set('Authorization', `Basic ${credentials}`) + .expect(200); + + expect(user.username).to.eql(adminTestUser.username); + expect(user.authentication_provider).to.eql({ type: 'http', name: '__http__' }); + expect(user.authentication_type).to.eql('realm'); + + // Make sure we don't automatically create a session + expect(headers['set-cookie']).to.be(undefined); + }); + }); +} diff --git a/x-pack/test/security_api_integration/tests/http_no_auth_providers/index.ts b/x-pack/test/security_api_integration/tests/http_no_auth_providers/index.ts new file mode 100644 index 000000000000..652bcc419e24 --- /dev/null +++ b/x-pack/test/security_api_integration/tests/http_no_auth_providers/index.ts @@ -0,0 +1,15 @@ +/* + * 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. + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('security APIs - HTTP no authentication providers are enabled', function () { + this.tags('ciGroup6'); + loadTestFile(require.resolve('./authentication')); + }); +}