From 680dc12dfd86918fac2fec06950879591ac03369 Mon Sep 17 00:00:00 2001 From: Kurt Date: Mon, 22 Aug 2022 10:29:05 -0400 Subject: [PATCH 01/23] Adding Global Access Agreement --- x-pack/plugins/security/server/config.test.ts | 34 +++++++++++++ x-pack/plugins/security/server/config.ts | 50 ++++++++++++++++--- 2 files changed, 77 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/security/server/config.test.ts b/x-pack/plugins/security/server/config.test.ts index f0db13bc773d6..b2d75fc28bed3 100644 --- a/x-pack/plugins/security/server/config.test.ts +++ b/x-pack/plugins/security/server/config.test.ts @@ -1789,6 +1789,40 @@ describe('createConfig()', () => { ).toThrow('[audit.appender.1.layout]: expected at least one defined value but got [undefined]'); }); + describe('Global accessAgreement', () => { + it('should not override local access agreement', () => { + + const resultConfig = createConfig( + ConfigSchema.validate({ + authc: { + accessAgreement: { message: 'foo' }, + providers: { + basic: { + basic1: { order: 0, accessAgreement: { message: 'bar' }}, + }, + saml: { + saml1: { order: 1, realm: 'saml1', accessAgreement: { message: 'baz' } }, + saml2: { order: 2, realm: 'saml2' }, + }, + oidc: { + oidc1: { order: 3, realm: 'oidc1' }, + oidc2: { order: 4, realm: 'oidc2', accessAgreement: { message: 'qux' }} + }, + }, + }, + }), + loggingSystemMock.create().get(), + { isTLSEnabled: true } + ); + + expect(resultConfig.authc.providers.basic?.basic1.accessAgreement?.message).toBe('bar'); + expect(resultConfig.authc.providers.saml?.saml1.accessAgreement?.message).toBe('baz'); + expect(resultConfig.authc.providers.saml?.saml2.accessAgreement?.message).toBe('foo'); + expect(resultConfig.authc.providers.oidc?.oidc1.accessAgreement?.message).toBe('foo'); + expect(resultConfig.authc.providers.oidc?.oidc2.accessAgreement?.message).toBe('qux'); + }); + }); + describe('#getExpirationTimeouts', () => { function createMockConfig(config: Record = {}) { return createConfig(ConfigSchema.validate(config), loggingSystemMock.createLogger(), { diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index 8d31e8ec95bfd..dc23e0b776d07 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -83,6 +83,7 @@ function getUniqueProviderSchema>>( } type ProvidersConfigType = TypeOf; + const providersConfigSchema = schema.object( { basic: getUniqueProviderSchema('basic', { @@ -237,6 +238,7 @@ export const ConfigSchema = schema.object({ }), authc: schema.object({ selector: schema.object({ enabled: schema.maybe(schema.boolean()) }), + accessAgreement: schema.maybe(schema.object({message: schema.string()})), providers: schema.oneOf([schema.arrayOf(schema.string()), providersConfigSchema], { defaultValue: { basic: { @@ -306,6 +308,7 @@ export function createConfig( } let secureCookies = config.secureCookies; + if (!isTLSEnabled) { if (secureCookies) { logger.warn( @@ -322,22 +325,25 @@ export function createConfig( } const isUsingLegacyProvidersFormat = Array.isArray(config.authc.providers); - const providers = ( + + let providers = ( isUsingLegacyProvidersFormat ? [...new Set(config.authc.providers as Array)].reduce( (legacyProviders, providerType, order) => { legacyProviders[providerType] = { - [providerType]: - providerType === 'saml' || providerType === 'oidc' - ? { enabled: true, showInSelector: true, order, ...config.authc[providerType] } - : { enabled: true, showInSelector: true, order }, + [providerType]: providerType === 'saml' || providerType === 'oidc' ? + { enabled: true, showInSelector: true, order, ...config.authc[providerType] } : + { enabled: true, showInSelector: true, order }, }; + return legacyProviders; }, {} as Record ) : config.authc.providers - ) as ProvidersConfigType; + ) as ProvidersConfigType; + + const updatedProvidersWithAccessAgreement: Record = {}; // Remove disabled providers and sort the rest. const sortedProviders: Array<{ @@ -346,21 +352,50 @@ export function createConfig( order: number; hasAccessAgreement: boolean; }> = []; + for (const [type, providerGroup] of Object.entries(providers)) { for (const [name, { enabled, order, accessAgreement }] of Object.entries(providerGroup ?? {})) { + const hasLocalAccessAgreement: boolean = !!accessAgreement?.message; + const globalAccessAgreement = config.authc?.accessAgreement?.message; + + let currType = { + ...updatedProvidersWithAccessAgreement[type], + [name]: { + ...providerGroup[name], + } + }; + + if (!hasLocalAccessAgreement) { + if (globalAccessAgreement) { + currType[name] = { + ...currType[name], + accessAgreement: { + message: globalAccessAgreement + } + } + } + } + + updatedProvidersWithAccessAgreement[type] = currType + if (!enabled) { delete providerGroup![name]; } else { + + const hasAccessAgreement: boolean = hasLocalAccessAgreement || !!globalAccessAgreement; + sortedProviders.push({ type: type as any, name, order, - hasAccessAgreement: !!accessAgreement?.message, + hasAccessAgreement, }); } } } + providers = updatedProvidersWithAccessAgreement as ProvidersConfigType; + sortedProviders.sort(({ order: orderA }, { order: orderB }) => orderA < orderB ? -1 : orderA > orderB ? 1 : 0 ); @@ -391,6 +426,7 @@ export function createConfig( max: 10, }, } as AppenderConfigType); + return { ...config, audit: { From 055b6aea2a72b895460d5bd5af0dd8548b6156e6 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 22 Aug 2022 14:42:21 +0000 Subject: [PATCH 02/23] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- x-pack/plugins/security/server/config.test.ts | 5 ++-- x-pack/plugins/security/server/config.ts | 24 +++++++++---------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/security/server/config.test.ts b/x-pack/plugins/security/server/config.test.ts index b2d75fc28bed3..548ecb309cb04 100644 --- a/x-pack/plugins/security/server/config.test.ts +++ b/x-pack/plugins/security/server/config.test.ts @@ -1791,14 +1791,13 @@ describe('createConfig()', () => { describe('Global accessAgreement', () => { it('should not override local access agreement', () => { - const resultConfig = createConfig( ConfigSchema.validate({ authc: { accessAgreement: { message: 'foo' }, providers: { basic: { - basic1: { order: 0, accessAgreement: { message: 'bar' }}, + basic1: { order: 0, accessAgreement: { message: 'bar' } }, }, saml: { saml1: { order: 1, realm: 'saml1', accessAgreement: { message: 'baz' } }, @@ -1806,7 +1805,7 @@ describe('createConfig()', () => { }, oidc: { oidc1: { order: 3, realm: 'oidc1' }, - oidc2: { order: 4, realm: 'oidc2', accessAgreement: { message: 'qux' }} + oidc2: { order: 4, realm: 'oidc2', accessAgreement: { message: 'qux' } }, }, }, }, diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index dc23e0b776d07..1f9f38c204d4f 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -238,7 +238,7 @@ export const ConfigSchema = schema.object({ }), authc: schema.object({ selector: schema.object({ enabled: schema.maybe(schema.boolean()) }), - accessAgreement: schema.maybe(schema.object({message: schema.string()})), + accessAgreement: schema.maybe(schema.object({ message: schema.string() })), providers: schema.oneOf([schema.arrayOf(schema.string()), providersConfigSchema], { defaultValue: { basic: { @@ -331,9 +331,10 @@ export function createConfig( ? [...new Set(config.authc.providers as Array)].reduce( (legacyProviders, providerType, order) => { legacyProviders[providerType] = { - [providerType]: providerType === 'saml' || providerType === 'oidc' ? - { enabled: true, showInSelector: true, order, ...config.authc[providerType] } : - { enabled: true, showInSelector: true, order }, + [providerType]: + providerType === 'saml' || providerType === 'oidc' + ? { enabled: true, showInSelector: true, order, ...config.authc[providerType] } + : { enabled: true, showInSelector: true, order }, }; return legacyProviders; @@ -341,7 +342,7 @@ export function createConfig( {} as Record ) : config.authc.providers - ) as ProvidersConfigType; + ) as ProvidersConfigType; const updatedProvidersWithAccessAgreement: Record = {}; @@ -358,11 +359,11 @@ export function createConfig( const hasLocalAccessAgreement: boolean = !!accessAgreement?.message; const globalAccessAgreement = config.authc?.accessAgreement?.message; - let currType = { + const currType = { ...updatedProvidersWithAccessAgreement[type], [name]: { ...providerGroup[name], - } + }, }; if (!hasLocalAccessAgreement) { @@ -370,18 +371,17 @@ export function createConfig( currType[name] = { ...currType[name], accessAgreement: { - message: globalAccessAgreement - } - } + message: globalAccessAgreement, + }, + }; } } - updatedProvidersWithAccessAgreement[type] = currType + updatedProvidersWithAccessAgreement[type] = currType; if (!enabled) { delete providerGroup![name]; } else { - const hasAccessAgreement: boolean = hasLocalAccessAgreement || !!globalAccessAgreement; sortedProviders.push({ From f6135fc4a7248166f576d5df059ba6628716bf09 Mon Sep 17 00:00:00 2001 From: Kurt Date: Tue, 23 Aug 2022 13:53:50 -0400 Subject: [PATCH 03/23] Adding docs --- docs/settings/security-settings.asciidoc | 9 +++++++++ docs/user/security/access-agreement.asciidoc | 3 +++ 2 files changed, 12 insertions(+) diff --git a/docs/settings/security-settings.asciidoc b/docs/settings/security-settings.asciidoc index 6f7ada651ad3a..a13c36a323f06 100644 --- a/docs/settings/security-settings.asciidoc +++ b/docs/settings/security-settings.asciidoc @@ -42,6 +42,15 @@ xpack.security.authc: <3> Specifies the settings for the SAML authentication provider with a `saml1` name. <4> Specifies the settings for the SAML authentication provider with a `saml2` name. +[float] +[[authentication-access-agreement-settings]] +==== Configure a default access agreement + +xpack.security.authc.accessAgreement.message {ess-icon}:: +This setting specifies the access agreement text in Markdown format that will be used as the default access agreement for all providers that do not +specify a value for `xpack.security.authc.providers...accessAgreement.message`. +For more information, refer to <>. + [float] [[authentication-provider-settings]] ==== Valid settings for all authentication providers diff --git a/docs/user/security/access-agreement.asciidoc b/docs/user/security/access-agreement.asciidoc index 9d9a0bb61a90b..4e322dc86414d 100644 --- a/docs/user/security/access-agreement.asciidoc +++ b/docs/user/security/access-agreement.asciidoc @@ -6,6 +6,9 @@ Access agreement is a https://www.elastic.co/subscriptions[subscription feature] agreement before accessing {kib}. The agreement text supports Markdown format and can be specified using the `xpack.security.authc.providers...accessAgreement.message` setting. +A default access agreement setting can also be specified using `xpack.security.authc.accessAgreement.message`. +This message will be used for each provider that doesn't specify its own access agreement. + [NOTE] ============================================================================ You need to acknowledge the access agreement only once per session, and {kib} reports the acknowledgement in the audit logs. From 60e0c5fd7fd89ded6ac11ea3ee58f90bdc232f30 Mon Sep 17 00:00:00 2001 From: Kurt Date: Wed, 24 Aug 2022 18:18:30 -0400 Subject: [PATCH 04/23] PR Review changes --- docs/settings/security-settings.asciidoc | 18 ++++----- docs/user/security/access-agreement.asciidoc | 2 +- .../authentication/authentication_service.ts | 1 + .../authentication/authenticator.test.ts | 35 ++++++++++++++++ .../server/authentication/authenticator.ts | 24 +++++++---- x-pack/plugins/security/server/config.test.ts | 33 --------------- x-pack/plugins/security/server/config.ts | 30 ++------------ .../routes/views/access_agreement.test.ts | 40 ++++++++++++++++++- .../server/routes/views/access_agreement.ts | 23 +++++++---- 9 files changed, 119 insertions(+), 87 deletions(-) diff --git a/docs/settings/security-settings.asciidoc b/docs/settings/security-settings.asciidoc index a13c36a323f06..30c7d136e5517 100644 --- a/docs/settings/security-settings.asciidoc +++ b/docs/settings/security-settings.asciidoc @@ -42,15 +42,6 @@ xpack.security.authc: <3> Specifies the settings for the SAML authentication provider with a `saml1` name. <4> Specifies the settings for the SAML authentication provider with a `saml2` name. -[float] -[[authentication-access-agreement-settings]] -==== Configure a default access agreement - -xpack.security.authc.accessAgreement.message {ess-icon}:: -This setting specifies the access agreement text in Markdown format that will be used as the default access agreement for all providers that do not -specify a value for `xpack.security.authc.providers...accessAgreement.message`. -For more information, refer to <>. - [float] [[authentication-provider-settings]] ==== Valid settings for all authentication providers @@ -165,6 +156,15 @@ Adds a message accessible at the login UI with additional help information for t xpack.security.authc.selector.enabled {ess-icon}:: Determines if the login selector UI should be enabled. By default, this setting is set to `true` if more than one authentication provider is configured. +[float] +[[authentication-access-agreement-settings]] +==== Configure a default access agreement + +xpack.security.accessAgreement.message {ess-icon}:: +This setting specifies the access agreement text in Markdown format that will be used as the default access agreement for all providers that do not +specify a value for `xpack.security.authc.providers...accessAgreement.message`. +For more information, refer to <>. + [float] [[security-session-and-cookie-settings]] ==== Session and cookie security settings diff --git a/docs/user/security/access-agreement.asciidoc b/docs/user/security/access-agreement.asciidoc index 4e322dc86414d..5ad5030c50773 100644 --- a/docs/user/security/access-agreement.asciidoc +++ b/docs/user/security/access-agreement.asciidoc @@ -6,7 +6,7 @@ Access agreement is a https://www.elastic.co/subscriptions[subscription feature] agreement before accessing {kib}. The agreement text supports Markdown format and can be specified using the `xpack.security.authc.providers...accessAgreement.message` setting. -A default access agreement setting can also be specified using `xpack.security.authc.accessAgreement.message`. +A default access agreement setting can also be specified using `xpack.security.accessAgreement.message`. This message will be used for each provider that doesn't specify its own access agreement. [NOTE] diff --git a/x-pack/plugins/security/server/authentication/authentication_service.ts b/x-pack/plugins/security/server/authentication/authentication_service.ts index 50797c821e884..2a6e0c6b3f3fa 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.ts @@ -343,6 +343,7 @@ export class AuthenticationService { license: this.license, session, isElasticCloudDeployment, + accessAgreement: { message: config.accessAgreement?.message } }); return { diff --git a/x-pack/plugins/security/server/authentication/authenticator.test.ts b/x-pack/plugins/security/server/authentication/authenticator.test.ts index 6cd626d611ab8..e58c27d7feabe 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.test.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.test.ts @@ -48,10 +48,12 @@ function getMockOptions({ providers, http = {}, selector, + accessAgreementMessage, }: { providers?: Record | string[]; http?: Partial; selector?: AuthenticatorOptions['config']['authc']['selector']; + accessAgreementMessage?: string; } = {}) { const auditService = auditServiceMock.create(); auditLogger = auditLoggerMock.create(); @@ -73,6 +75,7 @@ function getMockOptions({ featureUsageService: securityFeatureUsageServiceMock.createStartContract(), userProfileService: userProfileServiceMock.createStart(), isElasticCloudDeployment: jest.fn().mockReturnValue(false), + accessAgreement: { message: accessAgreementMessage } }; } @@ -1918,6 +1921,38 @@ describe('Authenticator', () => { ); expect(auditLogger.log).not.toHaveBeenCalled(); }); + + + it('redirects to global Access Agreement when local Access Agreement is not configured.', async () => { + mockOptions = getMockOptions({ + providers: { + basic: { basic1: { order: 0, } }, + }, + accessAgreementMessage: 'Foo' + }); + + mockOptions.license.getFeatures.mockReturnValue({ allowAccessAgreement: true,} as SecurityLicenseFeatures); + + authenticator = new Authenticator(mockOptions); + + mockOptions.session.get.mockResolvedValue(mockSessVal); + mockOptions.session.extend.mockResolvedValue(mockSessVal); + + mockBasicAuthenticationProvider.authenticate.mockResolvedValue( + AuthenticationResult.succeeded(mockUser, { + authResponseHeaders: { 'WWW-Authenticate': 'Negotiate' }, + }) + ); + + const request = httpServerMock.createKibanaRequest(); + await expect(authenticator.authenticate(request)).resolves.toEqual( + AuthenticationResult.redirectTo( + '/mock-server-basepath/security/access_agreement?next=%2Fmock-server-basepath%2Fpath', + { user: mockUser, authResponseHeaders: { 'WWW-Authenticate': 'Negotiate' } } + ) + ); + expect(auditLogger.log).not.toHaveBeenCalled(); + }); }); describe('with Overwritten Session', () => { diff --git a/x-pack/plugins/security/server/authentication/authenticator.ts b/x-pack/plugins/security/server/authentication/authenticator.ts index 581c889d72f58..df54938fbd2f9 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.ts @@ -90,6 +90,7 @@ export interface AuthenticatorOptions { session: PublicMethodsOf; getServerBaseURL: () => string; isElasticCloudDeployment: () => boolean; + accessAgreement: { message?: string }; } /** @internal */ @@ -814,20 +815,26 @@ export class Authenticator { */ private shouldRedirectToAccessAgreement(sessionValue: SessionValue | null) { // Request should be redirected to Access Agreement UI only if all following conditions are met: + if (sessionValue == null) { + return false; + } // 1. Request can be redirected (not API call) + // 2. Request is authenticated, but user hasn't acknowledged access agreement in the current // session yet (based on the flag we store in the session) + const isAccessAgreementAlreadyAcknowledged: boolean = !sessionValue.accessAgreementAcknowledged; + // 3. Request is authenticated by the provider that has `accessAgreement` configured + const hasAccessAgreementConfigured = + (this.options.config.authc.providers as Record)[sessionValue.provider.type]?.[sessionValue.provider.name]?.accessAgreement || + !!this.options.accessAgreement.message; + // 4. Current license allows access agreement + const doesCurrentLicenseAllowAccessAgreement = this.options.license.getFeatures().allowAccessAgreement; + // 5. And it's not a request to the Access Agreement UI itself - return ( - sessionValue != null && - !sessionValue.accessAgreementAcknowledged && - (this.options.config.authc.providers as Record)[sessionValue.provider.type]?.[ - sessionValue.provider.name - ]?.accessAgreement && - this.options.license.getFeatures().allowAccessAgreement - ); + + return isAccessAgreementAlreadyAcknowledged && hasAccessAgreementConfigured && doesCurrentLicenseAllowAccessAgreement; } /** @@ -856,6 +863,7 @@ export class Authenticator { const isUpdatedSessionAuthenticated = isSessionAuthenticated(sessionUpdateResult?.value); let preAccessRedirectURL; + if (isUpdatedSessionAuthenticated && sessionUpdateResult?.overwritten) { this.logger.debug('Redirecting user to the overwritten session UI.'); preAccessRedirectURL = `${this.options.basePath.serverBasePath}${OVERWRITTEN_SESSION_ROUTE}`; diff --git a/x-pack/plugins/security/server/config.test.ts b/x-pack/plugins/security/server/config.test.ts index 548ecb309cb04..f0db13bc773d6 100644 --- a/x-pack/plugins/security/server/config.test.ts +++ b/x-pack/plugins/security/server/config.test.ts @@ -1789,39 +1789,6 @@ describe('createConfig()', () => { ).toThrow('[audit.appender.1.layout]: expected at least one defined value but got [undefined]'); }); - describe('Global accessAgreement', () => { - it('should not override local access agreement', () => { - const resultConfig = createConfig( - ConfigSchema.validate({ - authc: { - accessAgreement: { message: 'foo' }, - providers: { - basic: { - basic1: { order: 0, accessAgreement: { message: 'bar' } }, - }, - saml: { - saml1: { order: 1, realm: 'saml1', accessAgreement: { message: 'baz' } }, - saml2: { order: 2, realm: 'saml2' }, - }, - oidc: { - oidc1: { order: 3, realm: 'oidc1' }, - oidc2: { order: 4, realm: 'oidc2', accessAgreement: { message: 'qux' } }, - }, - }, - }, - }), - loggingSystemMock.create().get(), - { isTLSEnabled: true } - ); - - expect(resultConfig.authc.providers.basic?.basic1.accessAgreement?.message).toBe('bar'); - expect(resultConfig.authc.providers.saml?.saml1.accessAgreement?.message).toBe('baz'); - expect(resultConfig.authc.providers.saml?.saml2.accessAgreement?.message).toBe('foo'); - expect(resultConfig.authc.providers.oidc?.oidc1.accessAgreement?.message).toBe('foo'); - expect(resultConfig.authc.providers.oidc?.oidc2.accessAgreement?.message).toBe('qux'); - }); - }); - describe('#getExpirationTimeouts', () => { function createMockConfig(config: Record = {}) { return createConfig(ConfigSchema.validate(config), loggingSystemMock.createLogger(), { diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index 1f9f38c204d4f..52b16ee09c469 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -236,9 +236,9 @@ export const ConfigSchema = schema.object({ hostname: schema.maybe(schema.string({ hostname: true })), port: schema.maybe(schema.number({ min: 0, max: 65535 })), }), + accessAgreement: schema.maybe(schema.object({ message: schema.string() })), authc: schema.object({ selector: schema.object({ enabled: schema.maybe(schema.boolean()) }), - accessAgreement: schema.maybe(schema.object({ message: schema.string() })), providers: schema.oneOf([schema.arrayOf(schema.string()), providersConfigSchema], { defaultValue: { basic: { @@ -344,8 +344,6 @@ export function createConfig( : config.authc.providers ) as ProvidersConfigType; - const updatedProvidersWithAccessAgreement: Record = {}; - // Remove disabled providers and sort the rest. const sortedProviders: Array<{ type: keyof ProvidersConfigType; @@ -357,32 +355,12 @@ export function createConfig( for (const [type, providerGroup] of Object.entries(providers)) { for (const [name, { enabled, order, accessAgreement }] of Object.entries(providerGroup ?? {})) { const hasLocalAccessAgreement: boolean = !!accessAgreement?.message; - const globalAccessAgreement = config.authc?.accessAgreement?.message; - - const currType = { - ...updatedProvidersWithAccessAgreement[type], - [name]: { - ...providerGroup[name], - }, - }; - - if (!hasLocalAccessAgreement) { - if (globalAccessAgreement) { - currType[name] = { - ...currType[name], - accessAgreement: { - message: globalAccessAgreement, - }, - }; - } - } - - updatedProvidersWithAccessAgreement[type] = currType; + const hasGlobalAccessAgreement: boolean = !!config.accessAgreement?.message; if (!enabled) { delete providerGroup![name]; } else { - const hasAccessAgreement: boolean = hasLocalAccessAgreement || !!globalAccessAgreement; + const hasAccessAgreement: boolean = hasLocalAccessAgreement || hasGlobalAccessAgreement; sortedProviders.push({ type: type as any, @@ -394,8 +372,6 @@ export function createConfig( } } - providers = updatedProvidersWithAccessAgreement as ProvidersConfigType; - sortedProviders.sort(({ order: orderA }, { order: orderB }) => orderA < orderB ? -1 : orderA > orderB ? 1 : 0 ); diff --git a/x-pack/plugins/security/server/routes/views/access_agreement.test.ts b/x-pack/plugins/security/server/routes/views/access_agreement.test.ts index 9b4bdb4d4329d..e33947ab9a1f2 100644 --- a/x-pack/plugins/security/server/routes/views/access_agreement.test.ts +++ b/x-pack/plugins/security/server/routes/views/access_agreement.test.ts @@ -31,6 +31,7 @@ describe('Access agreement view routes', () => { let session: jest.Mocked>; let license: jest.Mocked; let mockContext: SecurityRequestHandlerContext; + beforeEach(() => { const routeParamsMock = routeDefinitionParamsMock.create(); router = routeParamsMock.router; @@ -138,7 +139,7 @@ describe('Access agreement view routes', () => { }); }); - it('returns non-empty `accessAgreement` only if it is configured.', async () => { + it('returns non-empty local `accessAgreement` only if it is configured.', async () => { const request = httpServerMock.createKibanaRequest(); config.authc = routeDefinitionParamsMock.create({ @@ -172,5 +173,42 @@ describe('Access agreement view routes', () => { }); } }); + + it('returns global `accessAgreement` instead of local `accessAgreement` if it is not configured locally', async () => { + const request = httpServerMock.createKibanaRequest(); + + config.authc = routeDefinitionParamsMock.create({ + authc: { + providers: { + basic: { basic1: { order: 0 } }, + saml: { + saml1: { + order: 1, + realm: 'realm1', + accessAgreement: { message: 'Some access agreement' }, + }, + }, + }, + }, + }).config.authc; + + config.accessAgreement = {message: 'Foo'}; + + const cases: Array<[AuthenticationProvider, string]> = [ + [{ type: 'basic', name: 'basic1' }, 'Foo'], + [{ type: 'saml', name: 'saml1' }, 'Some access agreement'], + [{ type: 'unknown-type', name: 'unknown-name' }, 'Foo'], + ]; + + for (const [sessionProvider, expectedAccessAgreement] of cases) { + session.get.mockResolvedValue(sessionMock.createValue({ provider: sessionProvider })); + + await expect(routeHandler(mockContext, request, kibanaResponseFactory)).resolves.toEqual({ + options: { body: { accessAgreement: expectedAccessAgreement } }, + payload: { accessAgreement: expectedAccessAgreement }, + status: 200, + }); + } + }); }); }); diff --git a/x-pack/plugins/security/server/routes/views/access_agreement.ts b/x-pack/plugins/security/server/routes/views/access_agreement.ts index 83e640f43dee9..946e885b7df77 100644 --- a/x-pack/plugins/security/server/routes/views/access_agreement.ts +++ b/x-pack/plugins/security/server/routes/views/access_agreement.ts @@ -47,14 +47,21 @@ export function defineAccessAgreementRoutes({ // authenticated with the help of HTTP authentication), that means we should safely check if // we have it and can get a corresponding configuration. const sessionValue = await getSession().get(request); - const accessAgreement = - (sessionValue && - config.authc.providers[ - sessionValue.provider.type as keyof ConfigType['authc']['providers'] - ]?.[sessionValue.provider.name]?.accessAgreement?.message) || - ''; - - return response.ok({ body: { accessAgreement } }); + + let accessAgreement: string = ''; + + if (sessionValue) { + const localAccessAgreement = config.authc.providers[sessionValue.provider.type as keyof ConfigType['authc']['providers']]?.[sessionValue.provider.name]?.accessAgreement?.message; + const globalAccessAgreement = config.accessAgreement?.message; + + if (localAccessAgreement) { + accessAgreement = localAccessAgreement; + } else if (globalAccessAgreement) { + accessAgreement = globalAccessAgreement; + } + } + + return response.ok({ body: {accessAgreement} }); }) ); } From af8a6fd2744d25bcd0e52f1ed89a2b5e6034e298 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 24 Aug 2022 22:25:15 +0000 Subject: [PATCH 05/23] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- .../authentication/authentication_service.ts | 2 +- .../server/authentication/authenticator.test.ts | 11 ++++++----- .../server/authentication/authenticator.ts | 14 ++++++++++---- x-pack/plugins/security/server/config.ts | 2 +- .../server/routes/views/access_agreement.test.ts | 2 +- .../server/routes/views/access_agreement.ts | 7 +++++-- 6 files changed, 24 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/security/server/authentication/authentication_service.ts b/x-pack/plugins/security/server/authentication/authentication_service.ts index 2a6e0c6b3f3fa..b00bcc6b52ca8 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.ts @@ -343,7 +343,7 @@ export class AuthenticationService { license: this.license, session, isElasticCloudDeployment, - accessAgreement: { message: config.accessAgreement?.message } + accessAgreement: { message: config.accessAgreement?.message }, }); return { diff --git a/x-pack/plugins/security/server/authentication/authenticator.test.ts b/x-pack/plugins/security/server/authentication/authenticator.test.ts index e58c27d7feabe..d04170db2d20f 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.test.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.test.ts @@ -75,7 +75,7 @@ function getMockOptions({ featureUsageService: securityFeatureUsageServiceMock.createStartContract(), userProfileService: userProfileServiceMock.createStart(), isElasticCloudDeployment: jest.fn().mockReturnValue(false), - accessAgreement: { message: accessAgreementMessage } + accessAgreement: { message: accessAgreementMessage }, }; } @@ -1922,16 +1922,17 @@ describe('Authenticator', () => { expect(auditLogger.log).not.toHaveBeenCalled(); }); - it('redirects to global Access Agreement when local Access Agreement is not configured.', async () => { mockOptions = getMockOptions({ providers: { - basic: { basic1: { order: 0, } }, + basic: { basic1: { order: 0 } }, }, - accessAgreementMessage: 'Foo' + accessAgreementMessage: 'Foo', }); - mockOptions.license.getFeatures.mockReturnValue({ allowAccessAgreement: true,} as SecurityLicenseFeatures); + mockOptions.license.getFeatures.mockReturnValue({ + allowAccessAgreement: true, + } as SecurityLicenseFeatures); authenticator = new Authenticator(mockOptions); diff --git a/x-pack/plugins/security/server/authentication/authenticator.ts b/x-pack/plugins/security/server/authentication/authenticator.ts index df54938fbd2f9..6bc9575d5decc 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.ts @@ -826,15 +826,21 @@ export class Authenticator { // 3. Request is authenticated by the provider that has `accessAgreement` configured const hasAccessAgreementConfigured = - (this.options.config.authc.providers as Record)[sessionValue.provider.type]?.[sessionValue.provider.name]?.accessAgreement || - !!this.options.accessAgreement.message; + (this.options.config.authc.providers as Record)[sessionValue.provider.type]?.[ + sessionValue.provider.name + ]?.accessAgreement || !!this.options.accessAgreement.message; // 4. Current license allows access agreement - const doesCurrentLicenseAllowAccessAgreement = this.options.license.getFeatures().allowAccessAgreement; + const doesCurrentLicenseAllowAccessAgreement = + this.options.license.getFeatures().allowAccessAgreement; // 5. And it's not a request to the Access Agreement UI itself - return isAccessAgreementAlreadyAcknowledged && hasAccessAgreementConfigured && doesCurrentLicenseAllowAccessAgreement; + return ( + isAccessAgreementAlreadyAcknowledged && + hasAccessAgreementConfigured && + doesCurrentLicenseAllowAccessAgreement + ); } /** diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index 52b16ee09c469..0c85d62ec1755 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -326,7 +326,7 @@ export function createConfig( const isUsingLegacyProvidersFormat = Array.isArray(config.authc.providers); - let providers = ( + const providers = ( isUsingLegacyProvidersFormat ? [...new Set(config.authc.providers as Array)].reduce( (legacyProviders, providerType, order) => { diff --git a/x-pack/plugins/security/server/routes/views/access_agreement.test.ts b/x-pack/plugins/security/server/routes/views/access_agreement.test.ts index e33947ab9a1f2..9254f3687dd06 100644 --- a/x-pack/plugins/security/server/routes/views/access_agreement.test.ts +++ b/x-pack/plugins/security/server/routes/views/access_agreement.test.ts @@ -192,7 +192,7 @@ describe('Access agreement view routes', () => { }, }).config.authc; - config.accessAgreement = {message: 'Foo'}; + config.accessAgreement = { message: 'Foo' }; const cases: Array<[AuthenticationProvider, string]> = [ [{ type: 'basic', name: 'basic1' }, 'Foo'], diff --git a/x-pack/plugins/security/server/routes/views/access_agreement.ts b/x-pack/plugins/security/server/routes/views/access_agreement.ts index 946e885b7df77..7b1f81bb2219e 100644 --- a/x-pack/plugins/security/server/routes/views/access_agreement.ts +++ b/x-pack/plugins/security/server/routes/views/access_agreement.ts @@ -51,7 +51,10 @@ export function defineAccessAgreementRoutes({ let accessAgreement: string = ''; if (sessionValue) { - const localAccessAgreement = config.authc.providers[sessionValue.provider.type as keyof ConfigType['authc']['providers']]?.[sessionValue.provider.name]?.accessAgreement?.message; + const localAccessAgreement = + config.authc.providers[ + sessionValue.provider.type as keyof ConfigType['authc']['providers'] + ]?.[sessionValue.provider.name]?.accessAgreement?.message; const globalAccessAgreement = config.accessAgreement?.message; if (localAccessAgreement) { @@ -61,7 +64,7 @@ export function defineAccessAgreementRoutes({ } } - return response.ok({ body: {accessAgreement} }); + return response.ok({ body: { accessAgreement } }); }) ); } From a3f62501007ce52789bc8252921d008597872411 Mon Sep 17 00:00:00 2001 From: Kurt Date: Fri, 26 Aug 2022 09:28:51 -0400 Subject: [PATCH 06/23] PR Review changes --- .../authentication/authentication_service.ts | 6 ++- .../server/authentication/authenticator.ts | 38 +++++++------------ x-pack/plugins/security/server/config.ts | 4 +- .../server/routes/views/access_agreement.ts | 7 ++-- 4 files changed, 23 insertions(+), 32 deletions(-) diff --git a/x-pack/plugins/security/server/authentication/authentication_service.ts b/x-pack/plugins/security/server/authentication/authentication_service.ts index b00bcc6b52ca8..c22ac5fceecb6 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.ts @@ -335,7 +335,10 @@ export class AuthenticationService { loggers, clusterClient, basePath: http.basePath, - config: { authc: config.authc }, + config: { + authc: config.authc, + accessAgreement: config.accessAgreement, + }, getCurrentUser, featureUsageService, userProfileService, @@ -343,7 +346,6 @@ export class AuthenticationService { license: this.license, session, isElasticCloudDeployment, - accessAgreement: { message: config.accessAgreement?.message }, }); return { diff --git a/x-pack/plugins/security/server/authentication/authenticator.ts b/x-pack/plugins/security/server/authentication/authenticator.ts index 6bc9575d5decc..6820b5a4a4248 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.ts @@ -82,7 +82,7 @@ export interface AuthenticatorOptions { featureUsageService: SecurityFeatureUsageServiceStart; userProfileService: UserProfileServiceStartInternal; getCurrentUser: (request: KibanaRequest) => AuthenticatedUser | null; - config: Pick; + config: Pick; basePath: IBasePath; license: SecurityLicense; loggers: LoggerFactory; @@ -90,7 +90,6 @@ export interface AuthenticatorOptions { session: PublicMethodsOf; getServerBaseURL: () => string; isElasticCloudDeployment: () => boolean; - accessAgreement: { message?: string }; } /** @internal */ @@ -814,33 +813,22 @@ export class Authenticator { * @param sessionValue Current session value if any. */ private shouldRedirectToAccessAgreement(sessionValue: SessionValue | null) { - // Request should be redirected to Access Agreement UI only if all following conditions are met: - if (sessionValue == null) { + // If user doesn't have an active session or if they already acknowledged + // access agreement (based on the flag we store in the session) - bail out. + if (sessionValue == null || sessionValue.accessAgreementAcknowledged) { return false; } - // 1. Request can be redirected (not API call) - - // 2. Request is authenticated, but user hasn't acknowledged access agreement in the current - // session yet (based on the flag we store in the session) - const isAccessAgreementAlreadyAcknowledged: boolean = !sessionValue.accessAgreementAcknowledged; - - // 3. Request is authenticated by the provider that has `accessAgreement` configured - const hasAccessAgreementConfigured = - (this.options.config.authc.providers as Record)[sessionValue.provider.type]?.[ - sessionValue.provider.name - ]?.accessAgreement || !!this.options.accessAgreement.message; - - // 4. Current license allows access agreement - const doesCurrentLicenseAllowAccessAgreement = - this.options.license.getFeatures().allowAccessAgreement; - // 5. And it's not a request to the Access Agreement UI itself + // If access agreement is neither enabled globally (for all providers) + // nor for the provider that authenticated user request - bail out. + if ((this.options.config.authc.providers as Record)[sessionValue.provider.type]?.[ + sessionValue.provider.name + ]?.accessAgreement || !!this.options.config?.accessAgreement?.message) { + return false; + } - return ( - isAccessAgreementAlreadyAcknowledged && - hasAccessAgreementConfigured && - doesCurrentLicenseAllowAccessAgreement - ); + // Check if the current license allows access agreement. + return this.options.license.getFeatures().allowAccessAgreement; } /** diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index 0c85d62ec1755..0c96c7b4ae52f 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -354,13 +354,13 @@ export function createConfig( for (const [type, providerGroup] of Object.entries(providers)) { for (const [name, { enabled, order, accessAgreement }] of Object.entries(providerGroup ?? {})) { - const hasLocalAccessAgreement: boolean = !!accessAgreement?.message; + const hasProviderSpecificAccessAgreement: boolean = !!accessAgreement?.message; const hasGlobalAccessAgreement: boolean = !!config.accessAgreement?.message; if (!enabled) { delete providerGroup![name]; } else { - const hasAccessAgreement: boolean = hasLocalAccessAgreement || hasGlobalAccessAgreement; + const hasAccessAgreement: boolean = hasProviderSpecificAccessAgreement || hasGlobalAccessAgreement; sortedProviders.push({ type: type as any, diff --git a/x-pack/plugins/security/server/routes/views/access_agreement.ts b/x-pack/plugins/security/server/routes/views/access_agreement.ts index 7b1f81bb2219e..c8705ed3b2d07 100644 --- a/x-pack/plugins/security/server/routes/views/access_agreement.ts +++ b/x-pack/plugins/security/server/routes/views/access_agreement.ts @@ -51,14 +51,15 @@ export function defineAccessAgreementRoutes({ let accessAgreement: string = ''; if (sessionValue) { - const localAccessAgreement = + const providerSpecificAccessAgreement = config.authc.providers[ sessionValue.provider.type as keyof ConfigType['authc']['providers'] ]?.[sessionValue.provider.name]?.accessAgreement?.message; + const globalAccessAgreement = config.accessAgreement?.message; - if (localAccessAgreement) { - accessAgreement = localAccessAgreement; + if (providerSpecificAccessAgreement) { + accessAgreement = providerSpecificAccessAgreement; } else if (globalAccessAgreement) { accessAgreement = globalAccessAgreement; } From 809d4913f127260411fcd80c8093e755bc86bfe4 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 26 Aug 2022 13:35:50 +0000 Subject: [PATCH 07/23] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- .../security/server/authentication/authenticator.ts | 9 ++++++--- x-pack/plugins/security/server/config.ts | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/security/server/authentication/authenticator.ts b/x-pack/plugins/security/server/authentication/authenticator.ts index 6820b5a4a4248..e865c11a5d6b8 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.ts @@ -821,9 +821,12 @@ export class Authenticator { // If access agreement is neither enabled globally (for all providers) // nor for the provider that authenticated user request - bail out. - if ((this.options.config.authc.providers as Record)[sessionValue.provider.type]?.[ - sessionValue.provider.name - ]?.accessAgreement || !!this.options.config?.accessAgreement?.message) { + if ( + (this.options.config.authc.providers as Record)[sessionValue.provider.type]?.[ + sessionValue.provider.name + ]?.accessAgreement || + !!this.options.config?.accessAgreement?.message + ) { return false; } diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index 0c96c7b4ae52f..0f7967ef926e4 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -360,7 +360,8 @@ export function createConfig( if (!enabled) { delete providerGroup![name]; } else { - const hasAccessAgreement: boolean = hasProviderSpecificAccessAgreement || hasGlobalAccessAgreement; + const hasAccessAgreement: boolean = + hasProviderSpecificAccessAgreement || hasGlobalAccessAgreement; sortedProviders.push({ type: type as any, From 613ae7b6f4f20e86032ff74bd3c53072fc113f2e Mon Sep 17 00:00:00 2001 From: Kurt Date: Fri, 26 Aug 2022 14:59:09 -0400 Subject: [PATCH 08/23] Fixing global access agreement redirect logic --- .../security/server/authentication/authenticator.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/security/server/authentication/authenticator.ts b/x-pack/plugins/security/server/authentication/authenticator.ts index e865c11a5d6b8..fc313de3ed36c 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.ts @@ -821,12 +821,11 @@ export class Authenticator { // If access agreement is neither enabled globally (for all providers) // nor for the provider that authenticated user request - bail out. - if ( - (this.options.config.authc.providers as Record)[sessionValue.provider.type]?.[ - sessionValue.provider.name - ]?.accessAgreement || - !!this.options.config?.accessAgreement?.message - ) { + const providerConfig = (this.options.config.authc.providers as Record)[ + sessionValue.provider.type + ]?.[sessionValue.provider.name]; + + if (!this.options.config.accessAgreement && !providerConfig?.accessAgreement) { return false; } From a04f1667c0f2e999e69d858b6da7894734b4ec89 Mon Sep 17 00:00:00 2001 From: Kurt Date: Mon, 29 Aug 2022 09:48:33 -0400 Subject: [PATCH 09/23] Fixing unit test --- .../security/server/authentication/authenticator.test.ts | 6 ++++-- .../plugins/security/server/authentication/authenticator.ts | 2 +- x-pack/plugins/security/server/config.ts | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/security/server/authentication/authenticator.test.ts b/x-pack/plugins/security/server/authentication/authenticator.test.ts index d04170db2d20f..4b0faa7e7c26d 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.test.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.test.ts @@ -67,7 +67,10 @@ function getMockOptions({ loggers: loggingSystemMock.create(), getServerBaseURL: jest.fn(), config: createConfig( - ConfigSchema.validate({ authc: { selector, providers, http } }), + ConfigSchema.validate({ + authc: { selector, providers, http } , + accessAgreement: { message: accessAgreementMessage || undefined}, + }), loggingSystemMock.create().get(), { isTLSEnabled: false } ), @@ -75,7 +78,6 @@ function getMockOptions({ featureUsageService: securityFeatureUsageServiceMock.createStartContract(), userProfileService: userProfileServiceMock.createStart(), isElasticCloudDeployment: jest.fn().mockReturnValue(false), - accessAgreement: { message: accessAgreementMessage }, }; } diff --git a/x-pack/plugins/security/server/authentication/authenticator.ts b/x-pack/plugins/security/server/authentication/authenticator.ts index fc313de3ed36c..4ef78a736500a 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.ts @@ -825,7 +825,7 @@ export class Authenticator { sessionValue.provider.type ]?.[sessionValue.provider.name]; - if (!this.options.config.accessAgreement && !providerConfig?.accessAgreement) { + if (!this.options.config.accessAgreement?.message && !providerConfig?.accessAgreement?.message) { return false; } diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index 0f7967ef926e4..ed84dd86d2917 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -236,7 +236,7 @@ export const ConfigSchema = schema.object({ hostname: schema.maybe(schema.string({ hostname: true })), port: schema.maybe(schema.number({ min: 0, max: 65535 })), }), - accessAgreement: schema.maybe(schema.object({ message: schema.string() })), + accessAgreement: schema.maybe(schema.object({ message: schema.maybe(schema.string())})), authc: schema.object({ selector: schema.object({ enabled: schema.maybe(schema.boolean()) }), providers: schema.oneOf([schema.arrayOf(schema.string()), providersConfigSchema], { From fc8c260026abf6582daae3635ec9f1f2932762cb Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 29 Aug 2022 13:55:21 +0000 Subject: [PATCH 10/23] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- .../security/server/authentication/authenticator.test.ts | 4 ++-- .../plugins/security/server/authentication/authenticator.ts | 5 ++++- x-pack/plugins/security/server/config.ts | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/security/server/authentication/authenticator.test.ts b/x-pack/plugins/security/server/authentication/authenticator.test.ts index 4b0faa7e7c26d..eade18b8ef52b 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.test.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.test.ts @@ -68,8 +68,8 @@ function getMockOptions({ getServerBaseURL: jest.fn(), config: createConfig( ConfigSchema.validate({ - authc: { selector, providers, http } , - accessAgreement: { message: accessAgreementMessage || undefined}, + authc: { selector, providers, http }, + accessAgreement: { message: accessAgreementMessage || undefined }, }), loggingSystemMock.create().get(), { isTLSEnabled: false } diff --git a/x-pack/plugins/security/server/authentication/authenticator.ts b/x-pack/plugins/security/server/authentication/authenticator.ts index 4ef78a736500a..05bbcb690aef3 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.ts @@ -825,7 +825,10 @@ export class Authenticator { sessionValue.provider.type ]?.[sessionValue.provider.name]; - if (!this.options.config.accessAgreement?.message && !providerConfig?.accessAgreement?.message) { + if ( + !this.options.config.accessAgreement?.message && + !providerConfig?.accessAgreement?.message + ) { return false; } diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index ed84dd86d2917..501e724025847 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -236,7 +236,7 @@ export const ConfigSchema = schema.object({ hostname: schema.maybe(schema.string({ hostname: true })), port: schema.maybe(schema.number({ min: 0, max: 65535 })), }), - accessAgreement: schema.maybe(schema.object({ message: schema.maybe(schema.string())})), + accessAgreement: schema.maybe(schema.object({ message: schema.maybe(schema.string()) })), authc: schema.object({ selector: schema.object({ enabled: schema.maybe(schema.boolean()) }), providers: schema.oneOf([schema.arrayOf(schema.string()), providersConfigSchema], { From 85e8db57873d4aa390fca1ae08e5a5bc26f73e5e Mon Sep 17 00:00:00 2001 From: Kurt Date: Mon, 29 Aug 2022 11:10:56 -0400 Subject: [PATCH 11/23] Changing verbiage --- .../security/server/authentication/authenticator.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security/server/authentication/authenticator.test.ts b/x-pack/plugins/security/server/authentication/authenticator.test.ts index eade18b8ef52b..984ac72244311 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.test.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.test.ts @@ -1924,7 +1924,7 @@ describe('Authenticator', () => { expect(auditLogger.log).not.toHaveBeenCalled(); }); - it('redirects to global Access Agreement when local Access Agreement is not configured.', async () => { + it('redirects to global Access Agreement when provider specific Access Agreement is not configured.', async () => { mockOptions = getMockOptions({ providers: { basic: { basic1: { order: 0 } }, From 9aa9cb93c9ca059f68d48b779d569e92e21facd9 Mon Sep 17 00:00:00 2001 From: Kurt Date: Mon, 29 Aug 2022 12:17:17 -0400 Subject: [PATCH 12/23] Changing local -> provider specific --- .../security/server/routes/views/access_agreement.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security/server/routes/views/access_agreement.test.ts b/x-pack/plugins/security/server/routes/views/access_agreement.test.ts index 9254f3687dd06..5635dceace0b6 100644 --- a/x-pack/plugins/security/server/routes/views/access_agreement.test.ts +++ b/x-pack/plugins/security/server/routes/views/access_agreement.test.ts @@ -139,7 +139,7 @@ describe('Access agreement view routes', () => { }); }); - it('returns non-empty local `accessAgreement` only if it is configured.', async () => { + it('returns non-empty provider specific `accessAgreement` only if it is configured.', async () => { const request = httpServerMock.createKibanaRequest(); config.authc = routeDefinitionParamsMock.create({ @@ -174,7 +174,7 @@ describe('Access agreement view routes', () => { } }); - it('returns global `accessAgreement` instead of local `accessAgreement` if it is not configured locally', async () => { + it('returns global `accessAgreement` when provider specific `accessAgreement` is not configured', async () => { const request = httpServerMock.createKibanaRequest(); config.authc = routeDefinitionParamsMock.create({ From 6c059dd765d36ea3693d4f5dcfaabfe25e51ac08 Mon Sep 17 00:00:00 2001 From: Kurt Date: Wed, 31 Aug 2022 11:35:56 -0400 Subject: [PATCH 13/23] Update x-pack/plugins/security/server/routes/views/access_agreement.ts Co-authored-by: Aleh Zasypkin --- x-pack/plugins/security/server/routes/views/access_agreement.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security/server/routes/views/access_agreement.ts b/x-pack/plugins/security/server/routes/views/access_agreement.ts index c8705ed3b2d07..49a3984e181ac 100644 --- a/x-pack/plugins/security/server/routes/views/access_agreement.ts +++ b/x-pack/plugins/security/server/routes/views/access_agreement.ts @@ -48,7 +48,7 @@ export function defineAccessAgreementRoutes({ // we have it and can get a corresponding configuration. const sessionValue = await getSession().get(request); - let accessAgreement: string = ''; + let accessAgreement = ''; if (sessionValue) { const providerSpecificAccessAgreement = From 4ed8ce4a6d02f386e240084144f8d6702b0741c3 Mon Sep 17 00:00:00 2001 From: Kurt Date: Wed, 31 Aug 2022 15:38:17 -0400 Subject: [PATCH 14/23] PR Feedback --- x-pack/plugins/security/server/config.test.ts | 30 ++++++++++++++++++ x-pack/plugins/security/server/config.ts | 8 ++--- .../security_usage_collector.test.ts | 31 +++++++++++++++++++ .../security_usage_collector.ts | 3 +- 4 files changed, 65 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/security/server/config.test.ts b/x-pack/plugins/security/server/config.test.ts index f0db13bc773d6..5857d84809471 100644 --- a/x-pack/plugins/security/server/config.test.ts +++ b/x-pack/plugins/security/server/config.test.ts @@ -2204,4 +2204,34 @@ describe('createConfig()', () => { `); }); }); + + describe('Global Access Agreement', () => { + it('should require `message` for globally configured `accessAgreement`', () => { + expect(() => { + createConfig( + ConfigSchema.validate( + { + accessAgreement: {} + } + ), + loggingSystemMock.create().get(), + { isTLSEnabled: true } + )} + ).toThrow('[accessAgreement.message]: expected value of type [string] but got [undefined]'); + }); + + it('should accept string `message` for globally configured `accessAgreement`', () => { + expect( + createConfig( + ConfigSchema.validate( + { + accessAgreement: { message: 'Foo'} + } + ), + loggingSystemMock.create().get(), + { isTLSEnabled: true } + )?.accessAgreement?.message + ).toEqual('Foo'); + }); + }); }); diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index 501e724025847..aa7e3c0964ba8 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -236,7 +236,7 @@ export const ConfigSchema = schema.object({ hostname: schema.maybe(schema.string({ hostname: true })), port: schema.maybe(schema.number({ min: 0, max: 65535 })), }), - accessAgreement: schema.maybe(schema.object({ message: schema.maybe(schema.string()) })), + accessAgreement: schema.maybe(schema.object({ message: schema.string() })), authc: schema.object({ selector: schema.object({ enabled: schema.maybe(schema.boolean()) }), providers: schema.oneOf([schema.arrayOf(schema.string()), providersConfigSchema], { @@ -354,14 +354,10 @@ export function createConfig( for (const [type, providerGroup] of Object.entries(providers)) { for (const [name, { enabled, order, accessAgreement }] of Object.entries(providerGroup ?? {})) { - const hasProviderSpecificAccessAgreement: boolean = !!accessAgreement?.message; - const hasGlobalAccessAgreement: boolean = !!config.accessAgreement?.message; - if (!enabled) { delete providerGroup![name]; } else { - const hasAccessAgreement: boolean = - hasProviderSpecificAccessAgreement || hasGlobalAccessAgreement; + const hasAccessAgreement: boolean = !!accessAgreement?.message; sortedProviders.push({ type: type as any, diff --git a/x-pack/plugins/security/server/usage_collector/security_usage_collector.test.ts b/x-pack/plugins/security/server/usage_collector/security_usage_collector.test.ts index 15a0713d80326..f432105bd0280 100644 --- a/x-pack/plugins/security/server/usage_collector/security_usage_collector.test.ts +++ b/x-pack/plugins/security/server/usage_collector/security_usage_collector.test.ts @@ -200,6 +200,37 @@ describe('Security UsageCollector', () => { }); describe('access agreement', () => { + it.only('reports if the global access agreement message is configured', async () => { + const config = createSecurityConfig( + ConfigSchema.validate({ + accessAgreement: { message: 'Bar'}, + authc: { + providers: { + saml: { + saml1: { + realm: 'foo', + order: 1, + }, + }, + }, + }, + }) + ); + const usageCollection = usageCollectionPluginMock.createSetupContract(); + const license = createSecurityLicense({ isLicenseAvailable: true }); + registerSecurityUsageCollector({ usageCollection, config, license }); + + const usage = await usageCollection + .getCollectorByType('security') + ?.fetch(collectorFetchContext); + + expect(usage).toEqual({ + ...DEFAULT_USAGE, + accessAgreementEnabled: true, + enabledAuthProviders: ['saml'], + }); + }); + it('reports if the access agreement message is configured for any provider', async () => { const config = createSecurityConfig( ConfigSchema.validate({ diff --git a/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts b/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts index 4050e70bbcfed..097801928825d 100644 --- a/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts +++ b/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts @@ -161,7 +161,8 @@ export function registerSecurityUsageCollector({ usageCollection, config, licens ]; const accessAgreementEnabled = allowAccessAgreement && - config.authc.sortedProviders.some((provider) => provider.hasAccessAgreement); + (!!config.accessAgreement?.message || + config.authc.sortedProviders.some((provider) => provider.hasAccessAgreement)); const httpAuthSchemes = config.authc.http.schemes.filter((scheme) => WELL_KNOWN_AUTH_SCHEMES.includes(scheme.toLowerCase()) From e70f02e269ab1bb74c2e38c0c63f449f1ac387c1 Mon Sep 17 00:00:00 2001 From: Kurt Date: Wed, 31 Aug 2022 16:01:17 -0400 Subject: [PATCH 15/23] removing `only` --- .../server/usage_collector/security_usage_collector.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security/server/usage_collector/security_usage_collector.test.ts b/x-pack/plugins/security/server/usage_collector/security_usage_collector.test.ts index f432105bd0280..d3f394ae93085 100644 --- a/x-pack/plugins/security/server/usage_collector/security_usage_collector.test.ts +++ b/x-pack/plugins/security/server/usage_collector/security_usage_collector.test.ts @@ -200,7 +200,7 @@ describe('Security UsageCollector', () => { }); describe('access agreement', () => { - it.only('reports if the global access agreement message is configured', async () => { + it('reports if the global access agreement message is configured', async () => { const config = createSecurityConfig( ConfigSchema.validate({ accessAgreement: { message: 'Bar'}, From 255a56a1032f24109ce3db3f3eabf148253611ba Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 31 Aug 2022 20:07:59 +0000 Subject: [PATCH 16/23] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- .../server/usage_collector/security_usage_collector.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security/server/usage_collector/security_usage_collector.test.ts b/x-pack/plugins/security/server/usage_collector/security_usage_collector.test.ts index d3f394ae93085..286aed84527af 100644 --- a/x-pack/plugins/security/server/usage_collector/security_usage_collector.test.ts +++ b/x-pack/plugins/security/server/usage_collector/security_usage_collector.test.ts @@ -203,7 +203,7 @@ describe('Security UsageCollector', () => { it('reports if the global access agreement message is configured', async () => { const config = createSecurityConfig( ConfigSchema.validate({ - accessAgreement: { message: 'Bar'}, + accessAgreement: { message: 'Bar' }, authc: { providers: { saml: { From 9c77a543e1aed0980f9a09964f0cb8ba266d62c6 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 31 Aug 2022 20:35:29 +0000 Subject: [PATCH 17/23] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- x-pack/plugins/security/server/config.test.ts | 20 ++++++++----------- .../security_usage_collector.ts | 2 +- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/security/server/config.test.ts b/x-pack/plugins/security/server/config.test.ts index 5857d84809471..1c50c82d3a0f6 100644 --- a/x-pack/plugins/security/server/config.test.ts +++ b/x-pack/plugins/security/server/config.test.ts @@ -2209,25 +2209,21 @@ describe('createConfig()', () => { it('should require `message` for globally configured `accessAgreement`', () => { expect(() => { createConfig( - ConfigSchema.validate( - { - accessAgreement: {} - } - ), + ConfigSchema.validate({ + accessAgreement: {}, + }), loggingSystemMock.create().get(), { isTLSEnabled: true } - )} - ).toThrow('[accessAgreement.message]: expected value of type [string] but got [undefined]'); + ); + }).toThrow('[accessAgreement.message]: expected value of type [string] but got [undefined]'); }); it('should accept string `message` for globally configured `accessAgreement`', () => { expect( createConfig( - ConfigSchema.validate( - { - accessAgreement: { message: 'Foo'} - } - ), + ConfigSchema.validate({ + accessAgreement: { message: 'Foo' }, + }), loggingSystemMock.create().get(), { isTLSEnabled: true } )?.accessAgreement?.message diff --git a/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts b/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts index 097801928825d..d33ff8617e453 100644 --- a/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts +++ b/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts @@ -162,7 +162,7 @@ export function registerSecurityUsageCollector({ usageCollection, config, licens const accessAgreementEnabled = allowAccessAgreement && (!!config.accessAgreement?.message || - config.authc.sortedProviders.some((provider) => provider.hasAccessAgreement)); + config.authc.sortedProviders.some((provider) => provider.hasAccessAgreement)); const httpAuthSchemes = config.authc.http.schemes.filter((scheme) => WELL_KNOWN_AUTH_SCHEMES.includes(scheme.toLowerCase()) From fcc2b9a69d26a495042267c09024e91ed7ba4389 Mon Sep 17 00:00:00 2001 From: Kurt Date: Wed, 31 Aug 2022 17:37:50 -0400 Subject: [PATCH 18/23] Changing the accessAgreement to be optional --- .../security/server/authentication/authenticator.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security/server/authentication/authenticator.test.ts b/x-pack/plugins/security/server/authentication/authenticator.test.ts index 984ac72244311..e4b875b89b273 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.test.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.test.ts @@ -58,6 +58,9 @@ function getMockOptions({ const auditService = auditServiceMock.create(); auditLogger = auditLoggerMock.create(); auditService.asScoped.mockReturnValue(auditLogger); + + const accessAgreementObj = accessAgreementMessage ? {accessAgreement: { message: accessAgreementMessage }} : null; + return { audit: auditService, getCurrentUser: jest.fn(), @@ -69,7 +72,7 @@ function getMockOptions({ config: createConfig( ConfigSchema.validate({ authc: { selector, providers, http }, - accessAgreement: { message: accessAgreementMessage || undefined }, + ...accessAgreementObj }), loggingSystemMock.create().get(), { isTLSEnabled: false } From a15e0aa44fcc17ddfb56bdb382b9335d100ca062 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 31 Aug 2022 21:44:21 +0000 Subject: [PATCH 19/23] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- .../security/server/authentication/authenticator.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security/server/authentication/authenticator.test.ts b/x-pack/plugins/security/server/authentication/authenticator.test.ts index e4b875b89b273..05b31684ff72a 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.test.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.test.ts @@ -59,7 +59,9 @@ function getMockOptions({ auditLogger = auditLoggerMock.create(); auditService.asScoped.mockReturnValue(auditLogger); - const accessAgreementObj = accessAgreementMessage ? {accessAgreement: { message: accessAgreementMessage }} : null; + const accessAgreementObj = accessAgreementMessage + ? { accessAgreement: { message: accessAgreementMessage } } + : null; return { audit: auditService, @@ -72,7 +74,7 @@ function getMockOptions({ config: createConfig( ConfigSchema.validate({ authc: { selector, providers, http }, - ...accessAgreementObj + ...accessAgreementObj, }), loggingSystemMock.create().get(), { isTLSEnabled: false } From 8b49adabcb5ba34779626c566358956c3c80203d Mon Sep 17 00:00:00 2001 From: Kurt Date: Thu, 1 Sep 2022 11:43:28 -0400 Subject: [PATCH 20/23] Adding docker config --- .../docker_generator/resources/base/bin/kibana-docker | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker index f40c129cd96ae..7cbe5fb53ec1e 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker @@ -341,6 +341,7 @@ kibana_vars=( xpack.reporting.roles.allow xpack.reporting.roles.enabled xpack.ruleRegistry.write.enabled + xpack.security.accessAgreement.message xpack.security.audit.appender.fileName xpack.security.audit.appender.layout.highlight xpack.security.audit.appender.layout.pattern From 6f7c9c99094e24cd37174fbc728ef9b917cabc9a Mon Sep 17 00:00:00 2001 From: Kurt Date: Tue, 6 Sep 2022 07:56:15 -0400 Subject: [PATCH 21/23] Update docs/user/security/access-agreement.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> --- docs/user/security/access-agreement.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/security/access-agreement.asciidoc b/docs/user/security/access-agreement.asciidoc index 5ad5030c50773..e51ae74ff0300 100644 --- a/docs/user/security/access-agreement.asciidoc +++ b/docs/user/security/access-agreement.asciidoc @@ -6,7 +6,7 @@ Access agreement is a https://www.elastic.co/subscriptions[subscription feature] agreement before accessing {kib}. The agreement text supports Markdown format and can be specified using the `xpack.security.authc.providers...accessAgreement.message` setting. -A default access agreement setting can also be specified using `xpack.security.accessAgreement.message`. +You can specify a default access agreement using the `xpack.security.accessAgreement.message` setting. This message will be used for each provider that doesn't specify its own access agreement. [NOTE] From 319c2c9ffe0b19af943900d2fa07b4969dcff8a3 Mon Sep 17 00:00:00 2001 From: Kurt Date: Tue, 6 Sep 2022 07:56:34 -0400 Subject: [PATCH 22/23] Update docs/user/security/access-agreement.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> --- docs/user/security/access-agreement.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/security/access-agreement.asciidoc b/docs/user/security/access-agreement.asciidoc index e51ae74ff0300..03e5a312937a5 100644 --- a/docs/user/security/access-agreement.asciidoc +++ b/docs/user/security/access-agreement.asciidoc @@ -7,7 +7,7 @@ agreement before accessing {kib}. The agreement text supports Markdown format an `xpack.security.authc.providers...accessAgreement.message` setting. You can specify a default access agreement using the `xpack.security.accessAgreement.message` setting. -This message will be used for each provider that doesn't specify its own access agreement. +This message will be used for each provider who doesn't specify an access agreement. [NOTE] ============================================================================ From 11d0ec654e57ee1e658ef6f2c45451e16730d337 Mon Sep 17 00:00:00 2001 From: Kurt Date: Tue, 6 Sep 2022 08:00:12 -0400 Subject: [PATCH 23/23] Adding PR Review feedback --- docs/settings/security-settings.asciidoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/settings/security-settings.asciidoc b/docs/settings/security-settings.asciidoc index 6bf7e3806cb99..941cfca92e603 100644 --- a/docs/settings/security-settings.asciidoc +++ b/docs/settings/security-settings.asciidoc @@ -166,6 +166,8 @@ Determines if the login selector UI should be enabled. By default, this setting [[authentication-access-agreement-settings]] ==== Configure a default access agreement +You can configure the following settings in the `kibana.yml` file. + xpack.security.accessAgreement.message {ess-icon}:: This setting specifies the access agreement text in Markdown format that will be used as the default access agreement for all providers that do not specify a value for `xpack.security.authc.providers...accessAgreement.message`.