From f98c5dab63b79d52f6df8e91f1c24c2b273208fd Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau Date: Mon, 22 Nov 2021 14:15:02 -0500 Subject: [PATCH 1/9] add telemetry on auth type --- .../nav_control/nav_control_service.tsx | 12 ++++- x-pack/plugins/security/server/plugin.ts | 4 ++ .../plugins/security/server/routes/index.ts | 4 ++ .../routes/telemetry/authentication_type.ts | 50 +++++++++++++++++++ .../security/server/routes/telemetry/index.ts | 13 +++++ 5 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/security/server/routes/telemetry/authentication_type.ts create mode 100644 x-pack/plugins/security/server/routes/telemetry/index.ts diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx index c1be6999c6472e..0d003dd887f3cf 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx @@ -112,7 +112,17 @@ export class SecurityNavControlService { private registerSecurityNavControl( core: Pick ) { - const currentUserPromise = this.authc.getCurrentUser(); + const currentUserPromise = this.authc.getCurrentUser().then((authenticatedUser) => { + console.log('### only one time ###'); + try { + core.http.post('/internal/security/telemetry/auth_type', { + body: JSON.stringify({ auth_type: authenticatedUser.authentication_type }), + }); + // eslint-disable-next-line no-empty + } catch {} + + return authenticatedUser; + }); core.chrome.navControls.registerRight({ order: 2000, mount: (el: HTMLElement) => { diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 80082e9b7dbce3..2933540af5be4a 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -286,6 +286,9 @@ export class SecurityPlugin this.registerDeprecations(core, license); + // Usage counter for telemetry + const usageCounter = usageCollection?.createUsageCounter('security'); + defineRoutes({ router: core.http.createRouter(), basePath: core.http.basePath, @@ -301,6 +304,7 @@ export class SecurityPlugin getFeatureUsageService: this.getFeatureUsageService, getAuthenticationService: this.getAuthentication, getAnonymousAccessService: this.getAnonymousAccess, + usageCounter, }); return Object.freeze({ diff --git a/x-pack/plugins/security/server/routes/index.ts b/x-pack/plugins/security/server/routes/index.ts index 6785fe57c6b326..885ca5e8d3cb60 100644 --- a/x-pack/plugins/security/server/routes/index.ts +++ b/x-pack/plugins/security/server/routes/index.ts @@ -9,6 +9,7 @@ import type { Observable } from 'rxjs'; import type { PublicMethodsOf } from '@kbn/utility-types'; import type { HttpResources, IBasePath, Logger } from 'src/core/server'; +import type { UsageCounter } from 'src/plugins/usage_collection/server'; import type { KibanaFeature } from '../../../features/server'; import type { SecurityLicense } from '../../common'; @@ -28,6 +29,7 @@ import { defineIndicesRoutes } from './indices'; import { defineRoleMappingRoutes } from './role_mapping'; import { defineSecurityCheckupGetStateRoutes } from './security_checkup'; import { defineSessionManagementRoutes } from './session_management'; +import { defineTelemetryRoutes } from './telemetry'; import { defineUsersRoutes } from './users'; import { defineViewRoutes } from './views'; @@ -48,6 +50,7 @@ export interface RouteDefinitionParams { getFeatureUsageService: () => SecurityFeatureUsageServiceStart; getAuthenticationService: () => InternalAuthenticationServiceStart; getAnonymousAccessService: () => AnonymousAccessServiceStart; + usageCounter?: UsageCounter; } export function defineRoutes(params: RouteDefinitionParams) { @@ -62,4 +65,5 @@ export function defineRoutes(params: RouteDefinitionParams) { defineDeprecationsRoutes(params); defineAnonymousAccessRoutes(params); defineSecurityCheckupGetStateRoutes(params); + defineTelemetryRoutes(params); } diff --git a/x-pack/plugins/security/server/routes/telemetry/authentication_type.ts b/x-pack/plugins/security/server/routes/telemetry/authentication_type.ts new file mode 100644 index 00000000000000..579fac27c9ec12 --- /dev/null +++ b/x-pack/plugins/security/server/routes/telemetry/authentication_type.ts @@ -0,0 +1,50 @@ +/* + * 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 { schema } from '@kbn/config-schema'; + +import type { RouteDefinitionParams } from '..'; +import { wrapIntoCustomErrorResponse } from '../../errors'; +import { createLicensedRouteHandler } from '../licensed_route_handler'; + +const COUNTER_TYPE = 'SecurityAuthType'; + +export function defineTelemetryOnAuthTypeRoutes({ router, usageCounter }: RouteDefinitionParams) { + router.post( + { + path: '/internal/security/telemetry/auth_type', + validate: { + body: schema.object({ + auth_type: schema.string(), + }), + }, + }, + createLicensedRouteHandler(async (context, request, response) => { + try { + const { auth_type: authType } = request.body; + + if (!authType || (authType && authType === '')) { + return response.badRequest({ + body: { message: `Authentication type attributes can not be empty` }, + }); + } + + if (usageCounter) { + usageCounter.incrementCounter({ + counterName: authType, + counterType: COUNTER_TYPE, + incrementBy: 1, + }); + } + + return response.noContent(); + } catch (error) { + return response.customError(wrapIntoCustomErrorResponse(error)); + } + }) + ); +} diff --git a/x-pack/plugins/security/server/routes/telemetry/index.ts b/x-pack/plugins/security/server/routes/telemetry/index.ts new file mode 100644 index 00000000000000..ccb5b6fac3fa4b --- /dev/null +++ b/x-pack/plugins/security/server/routes/telemetry/index.ts @@ -0,0 +1,13 @@ +/* + * 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 type { RouteDefinitionParams } from '../'; +import { defineTelemetryOnAuthTypeRoutes } from './authentication_type'; + +export function defineTelemetryRoutes(params: RouteDefinitionParams) { + defineTelemetryOnAuthTypeRoutes(params); +} From 392af2f18db7ccb3e6e6b6b5c80072cae1da9d45 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau Date: Mon, 22 Nov 2021 16:00:31 -0500 Subject: [PATCH 2/9] make sure to keep track who is doing the telemetry --- .../nav_control/nav_control_service.tsx | 54 +++++++++++++++---- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx index 0d003dd887f3cf..09172eb8bc1167 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx @@ -5,6 +5,8 @@ * 2.0. */ +import type { BroadcastChannel as BroadcastChannelType } from 'broadcast-channel'; +import { createLeaderElection } from 'broadcast-channel'; import { sortBy } from 'lodash'; import React from 'react'; import ReactDOM from 'react-dom'; @@ -41,7 +43,11 @@ export interface SecurityNavControlServiceStart { addUserMenuLinks: (newUserMenuLink: UserMenuLink[]) => void; } +const USER_IN_SESSION = 'user_in_session'; + export class SecurityNavControlService { + private channel?: BroadcastChannelType<{ userInSession: boolean }>; + private securityLicense!: SecurityLicense; private authc!: AuthenticationServiceSetup; private logoutUrl!: string; @@ -69,6 +75,7 @@ export class SecurityNavControlService { if (shouldRegisterNavControl) { this.registerSecurityNavControl(core); } + this.telemetryOnAuthType(core); } ); @@ -112,17 +119,7 @@ export class SecurityNavControlService { private registerSecurityNavControl( core: Pick ) { - const currentUserPromise = this.authc.getCurrentUser().then((authenticatedUser) => { - console.log('### only one time ###'); - try { - core.http.post('/internal/security/telemetry/auth_type', { - body: JSON.stringify({ auth_type: authenticatedUser.authentication_type }), - }); - // eslint-disable-next-line no-empty - } catch {} - - return authenticatedUser; - }); + const currentUserPromise = this.authc.getCurrentUser(); core.chrome.navControls.registerRight({ order: 2000, mount: (el: HTMLElement) => { @@ -151,4 +148,39 @@ export class SecurityNavControlService { private sortUserMenuLinks(userMenuLinks: UserMenuLink[]) { return sortBy(userMenuLinks, 'order'); } + + private async telemetryOnAuthType(core: Pick) { + try { + const { BroadcastChannel } = await import('broadcast-channel'); + const authenticatedUser = await this.authc.getCurrentUser(); + + if (!this.channel) { + const tenant = core.http.basePath.serverBasePath; + this.channel = new BroadcastChannel(`${tenant}/${USER_IN_SESSION}`, { + webWorkerSupport: false, + }); + } + + const elector = createLeaderElection(this.channel, { + fallbackInterval: 0, // optional configuration for how often will renegotiation for leader occur -> no need of renegotiation + responseTime: 1000, // optional configuration for how long will instances have to respond + }); + elector.awaitLeadership().then(() => { + if (elector.isLeader) { + core.http.post('/internal/security/telemetry/auth_type', { + body: JSON.stringify({ auth_type: authenticatedUser.authentication_type }), + }); + } + }); + + setTimeout(async () => { + if (!elector.isLeader) { + // Let's kill the non leader since we do not need to do telemetry if you are not a leader + await elector.die(); + } + }, 5000); + + // eslint-disable-next-line no-empty + } catch {} + } } From 98658197eea387fd1c03ae149ee4f5efa234a50a Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau Date: Mon, 22 Nov 2021 16:05:47 -0500 Subject: [PATCH 3/9] forget to close channel --- .../plugins/security/public/nav_control/nav_control_service.tsx | 2 ++ .../security/server/routes/telemetry/authentication_type.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx index 09172eb8bc1167..879a819ea5d33e 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx @@ -114,6 +114,7 @@ export class SecurityNavControlService { } this.navControlRegistered = false; this.stop$.next(); + this.channel?.close(); } private registerSecurityNavControl( @@ -176,6 +177,7 @@ export class SecurityNavControlService { setTimeout(async () => { if (!elector.isLeader) { // Let's kill the non leader since we do not need to do telemetry if you are not a leader + // but we are keeping the leader alive to tell the other one that they won't be leader await elector.die(); } }, 5000); diff --git a/x-pack/plugins/security/server/routes/telemetry/authentication_type.ts b/x-pack/plugins/security/server/routes/telemetry/authentication_type.ts index 579fac27c9ec12..4ceaada08e9c24 100644 --- a/x-pack/plugins/security/server/routes/telemetry/authentication_type.ts +++ b/x-pack/plugins/security/server/routes/telemetry/authentication_type.ts @@ -29,7 +29,7 @@ export function defineTelemetryOnAuthTypeRoutes({ router, usageCounter }: RouteD if (!authType || (authType && authType === '')) { return response.badRequest({ - body: { message: `Authentication type attributes can not be empty` }, + body: { message: `Authentication type attribute can not be empty` }, }); } From a59a2d677268978fb05e73894261584a7ea98f30 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau Date: Wed, 24 Nov 2021 18:22:20 -0500 Subject: [PATCH 4/9] simpler solution --- .../authentication/logout/logout_app.ts | 3 + .../nav_control/nav_control_service.tsx | 44 -------------- x-pack/plugins/security/public/plugin.tsx | 7 +++ .../security/public/telemetry/index.ts | 43 +++++++++++++ .../routes/telemetry/authentication_type.ts | 60 +++++++++++++++---- 5 files changed, 103 insertions(+), 54 deletions(-) create mode 100644 x-pack/plugins/security/public/telemetry/index.ts diff --git a/x-pack/plugins/security/public/authentication/logout/logout_app.ts b/x-pack/plugins/security/public/authentication/logout/logout_app.ts index 84fc510e96ced3..d5d54514be2b1a 100644 --- a/x-pack/plugins/security/public/authentication/logout/logout_app.ts +++ b/x-pack/plugins/security/public/authentication/logout/logout_app.ts @@ -8,6 +8,8 @@ import { i18n } from '@kbn/i18n'; import type { CoreSetup, HttpSetup } from 'src/core/public'; +import { SecurityTelemetryService } from '../../telemetry'; + interface CreateDeps { application: CoreSetup['application']; http: HttpSetup; @@ -24,6 +26,7 @@ export const logoutApp = Object.freeze({ appRoute: '/logout', async mount() { window.sessionStorage.clear(); + window.localStorage.removeItem(SecurityTelemetryService.KeyAuthType); // Redirect user to the server logout endpoint to complete logout. window.location.href = http.basePath.prepend( diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx index 879a819ea5d33e..c1be6999c6472e 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx @@ -5,8 +5,6 @@ * 2.0. */ -import type { BroadcastChannel as BroadcastChannelType } from 'broadcast-channel'; -import { createLeaderElection } from 'broadcast-channel'; import { sortBy } from 'lodash'; import React from 'react'; import ReactDOM from 'react-dom'; @@ -43,11 +41,7 @@ export interface SecurityNavControlServiceStart { addUserMenuLinks: (newUserMenuLink: UserMenuLink[]) => void; } -const USER_IN_SESSION = 'user_in_session'; - export class SecurityNavControlService { - private channel?: BroadcastChannelType<{ userInSession: boolean }>; - private securityLicense!: SecurityLicense; private authc!: AuthenticationServiceSetup; private logoutUrl!: string; @@ -75,7 +69,6 @@ export class SecurityNavControlService { if (shouldRegisterNavControl) { this.registerSecurityNavControl(core); } - this.telemetryOnAuthType(core); } ); @@ -114,7 +107,6 @@ export class SecurityNavControlService { } this.navControlRegistered = false; this.stop$.next(); - this.channel?.close(); } private registerSecurityNavControl( @@ -149,40 +141,4 @@ export class SecurityNavControlService { private sortUserMenuLinks(userMenuLinks: UserMenuLink[]) { return sortBy(userMenuLinks, 'order'); } - - private async telemetryOnAuthType(core: Pick) { - try { - const { BroadcastChannel } = await import('broadcast-channel'); - const authenticatedUser = await this.authc.getCurrentUser(); - - if (!this.channel) { - const tenant = core.http.basePath.serverBasePath; - this.channel = new BroadcastChannel(`${tenant}/${USER_IN_SESSION}`, { - webWorkerSupport: false, - }); - } - - const elector = createLeaderElection(this.channel, { - fallbackInterval: 0, // optional configuration for how often will renegotiation for leader occur -> no need of renegotiation - responseTime: 1000, // optional configuration for how long will instances have to respond - }); - elector.awaitLeadership().then(() => { - if (elector.isLeader) { - core.http.post('/internal/security/telemetry/auth_type', { - body: JSON.stringify({ auth_type: authenticatedUser.authentication_type }), - }); - } - }); - - setTimeout(async () => { - if (!elector.isLeader) { - // Let's kill the non leader since we do not need to do telemetry if you are not a leader - // but we are keeping the leader alive to tell the other one that they won't be leader - await elector.die(); - } - }, 5000); - - // eslint-disable-next-line no-empty - } catch {} - } } diff --git a/x-pack/plugins/security/public/plugin.tsx b/x-pack/plugins/security/public/plugin.tsx index c2860ec059b8d5..d76c1ce3c0e1d1 100644 --- a/x-pack/plugins/security/public/plugin.tsx +++ b/x-pack/plugins/security/public/plugin.tsx @@ -28,6 +28,7 @@ import type { SecurityNavControlServiceStart } from './nav_control'; import { SecurityNavControlService } from './nav_control'; import { SecurityCheckupService } from './security_checkup'; import { SessionExpired, SessionTimeout, UnauthorizedResponseHttpInterceptor } from './session'; +import { SecurityTelemetryService } from './telemetry'; import type { UiApi } from './ui_api'; import { getUiApi } from './ui_api'; @@ -63,6 +64,7 @@ export class SecurityPlugin private readonly managementService = new ManagementService(); private readonly securityCheckupService: SecurityCheckupService; private readonly anonymousAccessService = new AnonymousAccessService(); + private readonly securityTelemetryService = new SecurityTelemetryService(); private authc!: AuthenticationServiceSetup; constructor(private readonly initializerContext: PluginInitializerContext) { @@ -164,6 +166,11 @@ export class SecurityPlugin this.anonymousAccessService.start({ http: core.http }); } + this.authc.getCurrentUser().then(() => { + // Only start the telemetry when you are log in + this.securityTelemetryService.start({ http: core.http }); + }); + return { uiApi: getUiApi({ core }), navControlService: this.navControlService.start({ core }), diff --git a/x-pack/plugins/security/public/telemetry/index.ts b/x-pack/plugins/security/public/telemetry/index.ts new file mode 100644 index 00000000000000..bb6f45e36d6e81 --- /dev/null +++ b/x-pack/plugins/security/public/telemetry/index.ts @@ -0,0 +1,43 @@ +/* + * 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 type { HttpStart } from 'src/core/public'; + +interface SecurityTelemetryStartParams { + http: HttpStart; +} + +interface SecurityTelemetryAuthType { + username_hash: string; + timestamp: number; + auth_type: string; +} + +export class SecurityTelemetryService { + public static KeyAuthType = 'kibana-user-auth-type'; + + public async start({ http }: SecurityTelemetryStartParams) { + await this.postAuthTypeTelemetry(http); + } + + private async postAuthTypeTelemetry(http: HttpStart) { + try { + const telemetryAuthTypeStringify = localStorage.getItem(SecurityTelemetryService.KeyAuthType); + const telemetryAuthTypeObj = await http.post( + '/internal/security/telemetry/auth_type', + { + body: telemetryAuthTypeStringify, + } + ); + localStorage.setItem( + SecurityTelemetryService.KeyAuthType, + JSON.stringify(telemetryAuthTypeObj) + ); + // eslint-disable-next-line no-empty + } catch (exp) {} + } +} diff --git a/x-pack/plugins/security/server/routes/telemetry/authentication_type.ts b/x-pack/plugins/security/server/routes/telemetry/authentication_type.ts index 4ceaada08e9c24..444a6108a5a77f 100644 --- a/x-pack/plugins/security/server/routes/telemetry/authentication_type.ts +++ b/x-pack/plugins/security/server/routes/telemetry/authentication_type.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { createHash } from 'crypto'; + import { schema } from '@kbn/config-schema'; import type { RouteDefinitionParams } from '..'; @@ -13,35 +15,73 @@ import { createLicensedRouteHandler } from '../licensed_route_handler'; const COUNTER_TYPE = 'SecurityAuthType'; -export function defineTelemetryOnAuthTypeRoutes({ router, usageCounter }: RouteDefinitionParams) { +export function defineTelemetryOnAuthTypeRoutes({ + getAuthenticationService, + router, + usageCounter, +}: RouteDefinitionParams) { router.post( { path: '/internal/security/telemetry/auth_type', validate: { - body: schema.object({ - auth_type: schema.string(), - }), + body: schema.nullable( + schema.object({ + auth_type: schema.string(), + timestamp: schema.number(), + username_hash: schema.string(), + }) + ), }, }, createLicensedRouteHandler(async (context, request, response) => { try { - const { auth_type: authType } = request.body; + const { + auth_type: oldAuthType, + username_hash: oldUsernameHash, + timestamp: oldTimestamp, + } = request.body || { + auth_type: '', + username_hash: '', + timestamp: new Date().getTime(), + }; + const authUser = await getAuthenticationService().getCurrentUser(request); + const usernameHash = authUser + ? createHash('sha3-256').update(authUser?.username).digest('hex') + : ''; + let timestamp = new Date().getTime(); + const elapsedTimeInHrs = (timestamp - oldTimestamp) / (1000 * 60 * 60); - if (!authType || (authType && authType === '')) { + if ( + !authUser?.authentication_type || + (authUser?.authentication_type && authUser?.authentication_type === '') + ) { return response.badRequest({ - body: { message: `Authentication type attribute can not be empty` }, + body: { message: `Authentication type can not be empty` }, }); } - if (usageCounter) { + if ( + usageCounter && + (elapsedTimeInHrs >= 12 || + oldUsernameHash !== usernameHash || + oldAuthType !== authUser?.authentication_type) + ) { usageCounter.incrementCounter({ - counterName: authType, + counterName: authUser?.authentication_type, counterType: COUNTER_TYPE, incrementBy: 1, }); + } else { + timestamp = oldTimestamp; } - return response.noContent(); + return response.ok({ + body: { + auth_type: authUser?.authentication_type, + username_hash: usernameHash, + timestamp, + }, + }); } catch (error) { return response.customError(wrapIntoCustomErrorResponse(error)); } From 409ff342553935820c7fefc8c5567667368b1873 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau Date: Mon, 29 Nov 2021 10:17:01 -0500 Subject: [PATCH 5/9] clean up --- x-pack/plugins/security/public/plugin.tsx | 6 +++--- x-pack/plugins/security/public/telemetry/index.ts | 8 ++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/security/public/plugin.tsx b/x-pack/plugins/security/public/plugin.tsx index d76c1ce3c0e1d1..d2f1f88613e36d 100644 --- a/x-pack/plugins/security/public/plugin.tsx +++ b/x-pack/plugins/security/public/plugin.tsx @@ -166,9 +166,9 @@ export class SecurityPlugin this.anonymousAccessService.start({ http: core.http }); } - this.authc.getCurrentUser().then(() => { - // Only start the telemetry when you are log in - this.securityTelemetryService.start({ http: core.http }); + this.securityTelemetryService.start({ + http: core.http, + getCurrentUser: this.authc.getCurrentUser, }); return { diff --git a/x-pack/plugins/security/public/telemetry/index.ts b/x-pack/plugins/security/public/telemetry/index.ts index bb6f45e36d6e81..056f787f5641ea 100644 --- a/x-pack/plugins/security/public/telemetry/index.ts +++ b/x-pack/plugins/security/public/telemetry/index.ts @@ -7,8 +7,11 @@ import type { HttpStart } from 'src/core/public'; +import type { AuthenticatedUser } from '../../common'; + interface SecurityTelemetryStartParams { http: HttpStart; + getCurrentUser: () => Promise; } interface SecurityTelemetryAuthType { @@ -20,8 +23,9 @@ interface SecurityTelemetryAuthType { export class SecurityTelemetryService { public static KeyAuthType = 'kibana-user-auth-type'; - public async start({ http }: SecurityTelemetryStartParams) { - await this.postAuthTypeTelemetry(http); + public async start({ http, getCurrentUser }: SecurityTelemetryStartParams) { + // wait for the user to be authenticated before doing telemetry + getCurrentUser().then(() => this.postAuthTypeTelemetry(http)); } private async postAuthTypeTelemetry(http: HttpStart) { From f0de52f964a5166a6f325ffb84b0b9f9b0416d14 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau Date: Mon, 29 Nov 2021 15:01:05 -0500 Subject: [PATCH 6/9] add unit test --- .../telemetry/authentication_type.test.ts | 220 ++++++++++++++++++ .../routes/telemetry/authentication_type.ts | 5 +- 2 files changed, 223 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/security/server/routes/telemetry/authentication_type.test.ts diff --git a/x-pack/plugins/security/server/routes/telemetry/authentication_type.test.ts b/x-pack/plugins/security/server/routes/telemetry/authentication_type.test.ts new file mode 100644 index 00000000000000..a0ae90aba182ea --- /dev/null +++ b/x-pack/plugins/security/server/routes/telemetry/authentication_type.test.ts @@ -0,0 +1,220 @@ +/* + * 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 type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; +import type { RequestHandler } from 'src/core/server'; +import { kibanaResponseFactory } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; +import { usageCountersServiceMock } from 'src/plugins/usage_collection/server/usage_counters/usage_counters_service.mock'; + +import type { UsageCounter } from '../../../../../../src/plugins/usage_collection/server/usage_counters/usage_counter'; +import type { AuthenticatedUser } from '../../../common'; +import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock'; +import type { InternalAuthenticationServiceStart } from '../../authentication'; +import { authenticationServiceMock } from '../../authentication/authentication_service.mock'; +import type { SecurityRequestHandlerContext } from '../../types'; +import { routeDefinitionParamsMock } from '../index.mock'; +import { defineTelemetryOnAuthTypeRoutes } from './authentication_type'; + +const FAKE_TIMESTAMP = 1637665318135; +function getMockContext( + licenseCheckResult: { state: string; message?: string } = { state: 'valid' } +) { + return { + licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, + } as unknown as SecurityRequestHandlerContext; +} + +describe('Telemetry on auth type', () => { + const mockContext = getMockContext(); + let routeHandler: RequestHandler; + let authc: DeeplyMockedKeys; + + jest.useFakeTimers('modern').setSystemTime(FAKE_TIMESTAMP); + + describe('call incrementCounter', () => { + let mockUsageCounter: UsageCounter; + beforeEach(() => { + const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract(); + mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test'); + authc = authenticationServiceMock.createStart(); + authc.getCurrentUser.mockImplementation(() => mockAuthenticatedUser()); + const mockRouteDefinitionParams = { + ...routeDefinitionParamsMock.create(), + usageCounter: mockUsageCounter, + }; + mockRouteDefinitionParams.getAuthenticationService.mockReturnValue(authc); + defineTelemetryOnAuthTypeRoutes(mockRouteDefinitionParams); + + const [, telemetryOnAuthTypeRouteHandler] = + mockRouteDefinitionParams.router.post.mock.calls.find( + ([{ path }]) => path === '/internal/security/telemetry/auth_type' + )!; + routeHandler = telemetryOnAuthTypeRouteHandler; + }); + + it('if request body is equal to null.', async () => { + const request = httpServerMock.createKibanaRequest(); + const response = await routeHandler(mockContext, request, kibanaResponseFactory); + + expect(response.status).toBe(200); + expect(mockUsageCounter.incrementCounter).toHaveBeenCalledTimes(1); + expect(response.payload).toEqual({ + auth_type: 'realm', + timestamp: FAKE_TIMESTAMP, + username_hash: '8ac76453d769d4fd14b3f41ad4933f9bd64321972cd002de9b847e117435b08b', + }); + }); + + it('if elapsed time is above 12 hours', async () => { + const request = httpServerMock.createKibanaRequest({ + body: { + auth_type: 'realm', + timestamp: FAKE_TIMESTAMP - 13 * 60 * 60 * 1000, + username_hash: '8ac76453d769d4fd14b3f41ad4933f9bd64321972cd002de9b847e117435b08b', + }, + }); + const response = await routeHandler(mockContext, request, kibanaResponseFactory); + + expect(response.status).toBe(200); + expect(mockUsageCounter.incrementCounter).toHaveBeenCalledTimes(1); + expect(response.payload).toEqual({ + auth_type: 'realm', + timestamp: FAKE_TIMESTAMP, + username_hash: '8ac76453d769d4fd14b3f41ad4933f9bd64321972cd002de9b847e117435b08b', + }); + }); + + it('if authType changed', async () => { + const request = httpServerMock.createKibanaRequest({ + body: { + auth_type: 'token', + timestamp: FAKE_TIMESTAMP, + username_hash: '8ac76453d769d4fd14b3f41ad4933f9bd64321972cd002de9b847e117435b08b', + }, + }); + const response = await routeHandler(mockContext, request, kibanaResponseFactory); + + expect(response.status).toBe(200); + expect(mockUsageCounter.incrementCounter).toHaveBeenCalledTimes(1); + expect(response.payload).toEqual({ + auth_type: 'realm', + timestamp: FAKE_TIMESTAMP, + username_hash: '8ac76453d769d4fd14b3f41ad4933f9bd64321972cd002de9b847e117435b08b', + }); + }); + + it('if username changed', async () => { + const request = httpServerMock.createKibanaRequest({ + body: { + auth_type: 'token', + timestamp: FAKE_TIMESTAMP, + username_hash: '33c76453d769d4fd14b3f41ad4933f9bd64321972cd002de9b847e117435b08b', + }, + }); + const response = await routeHandler(mockContext, request, kibanaResponseFactory); + + expect(response.status).toBe(200); + expect(mockUsageCounter.incrementCounter).toHaveBeenCalledTimes(1); + expect(response.payload).toEqual({ + auth_type: 'realm', + timestamp: FAKE_TIMESTAMP, + username_hash: '8ac76453d769d4fd14b3f41ad4933f9bd64321972cd002de9b847e117435b08b', + }); + }); + }); + + describe('do NOT call incrementCounter', () => { + let mockUsageCounter: UsageCounter; + beforeEach(() => { + const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract(); + mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test'); + authc = authenticationServiceMock.createStart(); + authc.getCurrentUser.mockImplementation(() => mockAuthenticatedUser()); + const mockRouteDefinitionParams = { + ...routeDefinitionParamsMock.create(), + usageCounter: mockUsageCounter, + }; + mockRouteDefinitionParams.getAuthenticationService.mockReturnValue(authc); + defineTelemetryOnAuthTypeRoutes(mockRouteDefinitionParams); + + const [, telemetryOnAuthTypeRouteHandler] = + mockRouteDefinitionParams.router.post.mock.calls.find( + ([{ path }]) => path === '/internal/security/telemetry/auth_type' + )!; + routeHandler = telemetryOnAuthTypeRouteHandler; + }); + + it('when getAuthenticationService do not return auth type', async () => { + authc.getCurrentUser.mockImplementation( + () => + ({ + ...mockAuthenticatedUser(), + authentication_type: undefined, + } as unknown as AuthenticatedUser) + ); + const mockRouteDefinitionParams = { + ...routeDefinitionParamsMock.create(), + usageCounter: mockUsageCounter, + }; + mockRouteDefinitionParams.getAuthenticationService.mockReturnValue(authc); + defineTelemetryOnAuthTypeRoutes(mockRouteDefinitionParams); + + const [, telemetryOnAuthTypeRouteHandler] = + mockRouteDefinitionParams.router.post.mock.calls.find( + ([{ path }]) => path === '/internal/security/telemetry/auth_type' + )!; + routeHandler = telemetryOnAuthTypeRouteHandler; + + const request = httpServerMock.createKibanaRequest(); + const response = await routeHandler(mockContext, request, kibanaResponseFactory); + + expect(response.status).toBe(400); + expect(response.payload).toEqual({ message: 'Authentication type can not be empty' }); + expect(mockUsageCounter.incrementCounter).toHaveBeenCalledTimes(0); + }); + + it('if elapsed time is under 12 hours', async () => { + const oldTimestamp = FAKE_TIMESTAMP - 10 * 60 * 60 * 1000; + const request = httpServerMock.createKibanaRequest({ + body: { + auth_type: 'realm', + timestamp: oldTimestamp, + username_hash: '8ac76453d769d4fd14b3f41ad4933f9bd64321972cd002de9b847e117435b08b', + }, + }); + const response = await routeHandler(mockContext, request, kibanaResponseFactory); + + expect(response.status).toBe(200); + expect(mockUsageCounter.incrementCounter).toHaveBeenCalledTimes(0); + expect(response.payload).toEqual({ + auth_type: 'realm', + timestamp: oldTimestamp, + username_hash: '8ac76453d769d4fd14b3f41ad4933f9bd64321972cd002de9b847e117435b08b', + }); + }); + + it('if authType/username did not change', async () => { + const request = httpServerMock.createKibanaRequest({ + body: { + auth_type: 'realm', + timestamp: FAKE_TIMESTAMP, + username_hash: '8ac76453d769d4fd14b3f41ad4933f9bd64321972cd002de9b847e117435b08b', + }, + }); + const response = await routeHandler(mockContext, request, kibanaResponseFactory); + + expect(response.status).toBe(200); + expect(mockUsageCounter.incrementCounter).toHaveBeenCalledTimes(0); + expect(response.payload).toEqual({ + auth_type: 'realm', + timestamp: FAKE_TIMESTAMP, + username_hash: '8ac76453d769d4fd14b3f41ad4933f9bd64321972cd002de9b847e117435b08b', + }); + }); + }); +}); diff --git a/x-pack/plugins/security/server/routes/telemetry/authentication_type.ts b/x-pack/plugins/security/server/routes/telemetry/authentication_type.ts index 444a6108a5a77f..cdf0ed5cdd06d3 100644 --- a/x-pack/plugins/security/server/routes/telemetry/authentication_type.ts +++ b/x-pack/plugins/security/server/routes/telemetry/authentication_type.ts @@ -35,6 +35,7 @@ export function defineTelemetryOnAuthTypeRoutes({ }, createLicensedRouteHandler(async (context, request, response) => { try { + let timestamp = new Date().getTime(); const { auth_type: oldAuthType, username_hash: oldUsernameHash, @@ -42,13 +43,13 @@ export function defineTelemetryOnAuthTypeRoutes({ } = request.body || { auth_type: '', username_hash: '', - timestamp: new Date().getTime(), + timestamp, }; const authUser = await getAuthenticationService().getCurrentUser(request); const usernameHash = authUser ? createHash('sha3-256').update(authUser?.username).digest('hex') : ''; - let timestamp = new Date().getTime(); + const elapsedTimeInHrs = (timestamp - oldTimestamp) / (1000 * 60 * 60); if ( From d513c46c1a25bd67964943ab41f7515e69e0c21f Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau Date: Tue, 30 Nov 2021 10:59:22 -0500 Subject: [PATCH 7/9] review I --- .../authentication/logout/logout_app.ts | 2 +- x-pack/plugins/security/public/plugin.tsx | 11 ++- .../security/public/telemetry/index.ts | 47 ----------- .../security/public/usage_collection/index.ts | 77 +++++++++++++++++++ x-pack/plugins/security/server/plugin.ts | 5 +- .../plugins/security/server/routes/index.ts | 4 +- .../authentication_type.test.ts | 14 ++-- .../authentication_type.ts | 11 ++- .../{telemetry => usage_collection}/index.ts | 6 +- 9 files changed, 104 insertions(+), 73 deletions(-) delete mode 100644 x-pack/plugins/security/public/telemetry/index.ts create mode 100644 x-pack/plugins/security/public/usage_collection/index.ts rename x-pack/plugins/security/server/routes/{telemetry => usage_collection}/authentication_type.test.ts (93%) rename x-pack/plugins/security/server/routes/{telemetry => usage_collection}/authentication_type.ts (90%) rename x-pack/plugins/security/server/routes/{telemetry => usage_collection}/index.ts (58%) diff --git a/x-pack/plugins/security/public/authentication/logout/logout_app.ts b/x-pack/plugins/security/public/authentication/logout/logout_app.ts index d5d54514be2b1a..2245e4865b3256 100644 --- a/x-pack/plugins/security/public/authentication/logout/logout_app.ts +++ b/x-pack/plugins/security/public/authentication/logout/logout_app.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import type { CoreSetup, HttpSetup } from 'src/core/public'; -import { SecurityTelemetryService } from '../../telemetry'; +import { SecurityTelemetryService } from '../../usage_collection'; interface CreateDeps { application: CoreSetup['application']; diff --git a/x-pack/plugins/security/public/plugin.tsx b/x-pack/plugins/security/public/plugin.tsx index d2f1f88613e36d..4e858d0ba1d14e 100644 --- a/x-pack/plugins/security/public/plugin.tsx +++ b/x-pack/plugins/security/public/plugin.tsx @@ -28,9 +28,9 @@ import type { SecurityNavControlServiceStart } from './nav_control'; import { SecurityNavControlService } from './nav_control'; import { SecurityCheckupService } from './security_checkup'; import { SessionExpired, SessionTimeout, UnauthorizedResponseHttpInterceptor } from './session'; -import { SecurityTelemetryService } from './telemetry'; import type { UiApi } from './ui_api'; import { getUiApi } from './ui_api'; +import { SecurityUsageCollectionService } from './usage_collection'; export interface PluginSetupDependencies { licensing: LicensingPluginSetup; @@ -64,7 +64,7 @@ export class SecurityPlugin private readonly managementService = new ManagementService(); private readonly securityCheckupService: SecurityCheckupService; private readonly anonymousAccessService = new AnonymousAccessService(); - private readonly securityTelemetryService = new SecurityTelemetryService(); + private readonly securityUsageCollectionService = new SecurityUsageCollectionService(); private authc!: AuthenticationServiceSetup; constructor(private readonly initializerContext: PluginInitializerContext) { @@ -104,6 +104,10 @@ export class SecurityPlugin logoutUrl, }); + this.securityUsageCollectionService.setup({ + securityLicense: license, + }); + accountManagementApp.create({ authc: this.authc, application: core.application, @@ -166,7 +170,7 @@ export class SecurityPlugin this.anonymousAccessService.start({ http: core.http }); } - this.securityTelemetryService.start({ + this.securityUsageCollectionService.start({ http: core.http, getCurrentUser: this.authc.getCurrentUser, }); @@ -183,6 +187,7 @@ export class SecurityPlugin this.navControlService.stop(); this.securityLicenseService.stop(); this.managementService.stop(); + this.securityUsageCollectionService.stop(); } } diff --git a/x-pack/plugins/security/public/telemetry/index.ts b/x-pack/plugins/security/public/telemetry/index.ts deleted file mode 100644 index 056f787f5641ea..00000000000000 --- a/x-pack/plugins/security/public/telemetry/index.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 type { HttpStart } from 'src/core/public'; - -import type { AuthenticatedUser } from '../../common'; - -interface SecurityTelemetryStartParams { - http: HttpStart; - getCurrentUser: () => Promise; -} - -interface SecurityTelemetryAuthType { - username_hash: string; - timestamp: number; - auth_type: string; -} - -export class SecurityTelemetryService { - public static KeyAuthType = 'kibana-user-auth-type'; - - public async start({ http, getCurrentUser }: SecurityTelemetryStartParams) { - // wait for the user to be authenticated before doing telemetry - getCurrentUser().then(() => this.postAuthTypeTelemetry(http)); - } - - private async postAuthTypeTelemetry(http: HttpStart) { - try { - const telemetryAuthTypeStringify = localStorage.getItem(SecurityTelemetryService.KeyAuthType); - const telemetryAuthTypeObj = await http.post( - '/internal/security/telemetry/auth_type', - { - body: telemetryAuthTypeStringify, - } - ); - localStorage.setItem( - SecurityTelemetryService.KeyAuthType, - JSON.stringify(telemetryAuthTypeObj) - ); - // eslint-disable-next-line no-empty - } catch (exp) {} - } -} diff --git a/x-pack/plugins/security/public/usage_collection/index.ts b/x-pack/plugins/security/public/usage_collection/index.ts new file mode 100644 index 00000000000000..6d973d4dd31461 --- /dev/null +++ b/x-pack/plugins/security/public/usage_collection/index.ts @@ -0,0 +1,77 @@ +/* + * 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 type { Subscription } from 'rxjs'; + +import type { HttpStart } from 'src/core/public'; + +import type { AuthenticatedUser, SecurityLicense } from '../../common'; + +interface SecurityUsageCollectionSetupParams { + securityLicense: SecurityLicense; +} + +interface SecurityUsageCollectionStartParams { + http: HttpStart; + getCurrentUser: () => Promise; +} + +interface SecurityUsageCollectionAuthType { + username_hash: string; + timestamp: number; + auth_type: string; +} + +export class SecurityUsageCollectionService { + public static KeyAuthType = 'kibana-user-auth-type'; + private securityLicense!: SecurityLicense; + private securityFeaturesSubscription?: Subscription; + + public setup({ securityLicense }: SecurityUsageCollectionSetupParams) { + this.securityLicense = securityLicense; + } + + public async start({ http, getCurrentUser }: SecurityUsageCollectionStartParams) { + // wait for the user to be authenticated before doing UsageCollection + this.securityFeaturesSubscription = this.securityLicense.features$.subscribe( + ({ allowRbac }) => { + if (allowRbac) { + getCurrentUser().then(() => this.recordAuthTypeUsageCollection(http)); + } + } + ); + } + + public stop() { + if (this.securityFeaturesSubscription) { + this.securityFeaturesSubscription.unsubscribe(); + this.securityFeaturesSubscription = undefined; + } + } + + private async recordAuthTypeUsageCollection(http: HttpStart) { + try { + const UsageCollectionAuthTypeStringify = localStorage.getItem( + SecurityUsageCollectionService.KeyAuthType + ); + const UsageCollectionAuthTypeObj = await http.post( + '/internal/security/usage_collection/record_auth_type', + { + body: UsageCollectionAuthTypeStringify, + } + ); + if (UsageCollectionAuthTypeObj) { + localStorage.setItem( + SecurityUsageCollectionService.KeyAuthType, + JSON.stringify(UsageCollectionAuthTypeObj) + ); + } + } catch (exp) { + // do nothing + } + } +} diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 47fcc158e6d2eb..c1f82ab38b07ef 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -292,9 +292,6 @@ export class SecurityPlugin this.registerDeprecations(core, license); - // Usage counter for telemetry - const usageCounter = usageCollection?.createUsageCounter('security'); - defineRoutes({ router: core.http.createRouter(), basePath: core.http.basePath, @@ -310,7 +307,7 @@ export class SecurityPlugin getFeatureUsageService: this.getFeatureUsageService, getAuthenticationService: this.getAuthentication, getAnonymousAccessService: this.getAnonymousAccess, - usageCounter, + usageCounter: usageCollection?.createUsageCounter('security'), }); return Object.freeze({ diff --git a/x-pack/plugins/security/server/routes/index.ts b/x-pack/plugins/security/server/routes/index.ts index 885ca5e8d3cb60..61094d29dfaa0e 100644 --- a/x-pack/plugins/security/server/routes/index.ts +++ b/x-pack/plugins/security/server/routes/index.ts @@ -29,7 +29,7 @@ import { defineIndicesRoutes } from './indices'; import { defineRoleMappingRoutes } from './role_mapping'; import { defineSecurityCheckupGetStateRoutes } from './security_checkup'; import { defineSessionManagementRoutes } from './session_management'; -import { defineTelemetryRoutes } from './telemetry'; +import { defineUsageCollectionRoutes } from './usage_collection'; import { defineUsersRoutes } from './users'; import { defineViewRoutes } from './views'; @@ -65,5 +65,5 @@ export function defineRoutes(params: RouteDefinitionParams) { defineDeprecationsRoutes(params); defineAnonymousAccessRoutes(params); defineSecurityCheckupGetStateRoutes(params); - defineTelemetryRoutes(params); + defineUsageCollectionRoutes(params); } diff --git a/x-pack/plugins/security/server/routes/telemetry/authentication_type.test.ts b/x-pack/plugins/security/server/routes/usage_collection/authentication_type.test.ts similarity index 93% rename from x-pack/plugins/security/server/routes/telemetry/authentication_type.test.ts rename to x-pack/plugins/security/server/routes/usage_collection/authentication_type.test.ts index a0ae90aba182ea..b7d742fe36d03e 100644 --- a/x-pack/plugins/security/server/routes/telemetry/authentication_type.test.ts +++ b/x-pack/plugins/security/server/routes/usage_collection/authentication_type.test.ts @@ -18,7 +18,7 @@ import type { InternalAuthenticationServiceStart } from '../../authentication'; import { authenticationServiceMock } from '../../authentication/authentication_service.mock'; import type { SecurityRequestHandlerContext } from '../../types'; import { routeDefinitionParamsMock } from '../index.mock'; -import { defineTelemetryOnAuthTypeRoutes } from './authentication_type'; +import { defineRecordUsageCollectionOnAuthTypeRoutes } from './authentication_type'; const FAKE_TIMESTAMP = 1637665318135; function getMockContext( @@ -48,11 +48,11 @@ describe('Telemetry on auth type', () => { usageCounter: mockUsageCounter, }; mockRouteDefinitionParams.getAuthenticationService.mockReturnValue(authc); - defineTelemetryOnAuthTypeRoutes(mockRouteDefinitionParams); + defineRecordUsageCollectionOnAuthTypeRoutes(mockRouteDefinitionParams); const [, telemetryOnAuthTypeRouteHandler] = mockRouteDefinitionParams.router.post.mock.calls.find( - ([{ path }]) => path === '/internal/security/telemetry/auth_type' + ([{ path }]) => path === '/internal/security/usage_collection/record_auth_type' )!; routeHandler = telemetryOnAuthTypeRouteHandler; }); @@ -140,11 +140,11 @@ describe('Telemetry on auth type', () => { usageCounter: mockUsageCounter, }; mockRouteDefinitionParams.getAuthenticationService.mockReturnValue(authc); - defineTelemetryOnAuthTypeRoutes(mockRouteDefinitionParams); + defineRecordUsageCollectionOnAuthTypeRoutes(mockRouteDefinitionParams); const [, telemetryOnAuthTypeRouteHandler] = mockRouteDefinitionParams.router.post.mock.calls.find( - ([{ path }]) => path === '/internal/security/telemetry/auth_type' + ([{ path }]) => path === '/internal/security/usage_collection/record_auth_type' )!; routeHandler = telemetryOnAuthTypeRouteHandler; }); @@ -162,11 +162,11 @@ describe('Telemetry on auth type', () => { usageCounter: mockUsageCounter, }; mockRouteDefinitionParams.getAuthenticationService.mockReturnValue(authc); - defineTelemetryOnAuthTypeRoutes(mockRouteDefinitionParams); + defineRecordUsageCollectionOnAuthTypeRoutes(mockRouteDefinitionParams); const [, telemetryOnAuthTypeRouteHandler] = mockRouteDefinitionParams.router.post.mock.calls.find( - ([{ path }]) => path === '/internal/security/telemetry/auth_type' + ([{ path }]) => path === '/internal/security/usage_collection/record_auth_type' )!; routeHandler = telemetryOnAuthTypeRouteHandler; diff --git a/x-pack/plugins/security/server/routes/telemetry/authentication_type.ts b/x-pack/plugins/security/server/routes/usage_collection/authentication_type.ts similarity index 90% rename from x-pack/plugins/security/server/routes/telemetry/authentication_type.ts rename to x-pack/plugins/security/server/routes/usage_collection/authentication_type.ts index cdf0ed5cdd06d3..a6356b4b599996 100644 --- a/x-pack/plugins/security/server/routes/telemetry/authentication_type.ts +++ b/x-pack/plugins/security/server/routes/usage_collection/authentication_type.ts @@ -14,15 +14,16 @@ import { wrapIntoCustomErrorResponse } from '../../errors'; import { createLicensedRouteHandler } from '../licensed_route_handler'; const COUNTER_TYPE = 'SecurityAuthType'; +const MINIMUM_ELAPSED_TIME_HOURS = 12; -export function defineTelemetryOnAuthTypeRoutes({ +export function defineRecordUsageCollectionOnAuthTypeRoutes({ getAuthenticationService, router, usageCounter, }: RouteDefinitionParams) { router.post( { - path: '/internal/security/telemetry/auth_type', + path: '/internal/security/usage_collection/record_auth_type', validate: { body: schema.nullable( schema.object({ @@ -56,14 +57,12 @@ export function defineTelemetryOnAuthTypeRoutes({ !authUser?.authentication_type || (authUser?.authentication_type && authUser?.authentication_type === '') ) { - return response.badRequest({ - body: { message: `Authentication type can not be empty` }, - }); + return response.noContent(); } if ( usageCounter && - (elapsedTimeInHrs >= 12 || + (elapsedTimeInHrs >= MINIMUM_ELAPSED_TIME_HOURS || oldUsernameHash !== usernameHash || oldAuthType !== authUser?.authentication_type) ) { diff --git a/x-pack/plugins/security/server/routes/telemetry/index.ts b/x-pack/plugins/security/server/routes/usage_collection/index.ts similarity index 58% rename from x-pack/plugins/security/server/routes/telemetry/index.ts rename to x-pack/plugins/security/server/routes/usage_collection/index.ts index ccb5b6fac3fa4b..c07f24bf259bf6 100644 --- a/x-pack/plugins/security/server/routes/telemetry/index.ts +++ b/x-pack/plugins/security/server/routes/usage_collection/index.ts @@ -6,8 +6,8 @@ */ import type { RouteDefinitionParams } from '../'; -import { defineTelemetryOnAuthTypeRoutes } from './authentication_type'; +import { defineRecordUsageCollectionOnAuthTypeRoutes } from './authentication_type'; -export function defineTelemetryRoutes(params: RouteDefinitionParams) { - defineTelemetryOnAuthTypeRoutes(params); +export function defineUsageCollectionRoutes(params: RouteDefinitionParams) { + defineRecordUsageCollectionOnAuthTypeRoutes(params); } From c90f3fc6eeb5641490796da0de6facf26b374f2f Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau Date: Tue, 30 Nov 2021 13:22:05 -0500 Subject: [PATCH 8/9] missing type --- .../security/public/authentication/logout/logout_app.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security/public/authentication/logout/logout_app.ts b/x-pack/plugins/security/public/authentication/logout/logout_app.ts index 2245e4865b3256..03abb3bd270d82 100644 --- a/x-pack/plugins/security/public/authentication/logout/logout_app.ts +++ b/x-pack/plugins/security/public/authentication/logout/logout_app.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import type { CoreSetup, HttpSetup } from 'src/core/public'; -import { SecurityTelemetryService } from '../../usage_collection'; +import { SecurityUsageCollectionService } from '../../usage_collection'; interface CreateDeps { application: CoreSetup['application']; @@ -26,7 +26,7 @@ export const logoutApp = Object.freeze({ appRoute: '/logout', async mount() { window.sessionStorage.clear(); - window.localStorage.removeItem(SecurityTelemetryService.KeyAuthType); + window.localStorage.removeItem(SecurityUsageCollectionService.KeyAuthType); // Redirect user to the server logout endpoint to complete logout. window.location.href = http.basePath.prepend( From ddd836106c7f2ff1afae33c90868c7894c06f272 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau Date: Tue, 30 Nov 2021 15:22:35 -0500 Subject: [PATCH 9/9] fix jest --- .../routes/usage_collection/authentication_type.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security/server/routes/usage_collection/authentication_type.test.ts b/x-pack/plugins/security/server/routes/usage_collection/authentication_type.test.ts index b7d742fe36d03e..5631d94ecca1f2 100644 --- a/x-pack/plugins/security/server/routes/usage_collection/authentication_type.test.ts +++ b/x-pack/plugins/security/server/routes/usage_collection/authentication_type.test.ts @@ -173,8 +173,8 @@ describe('Telemetry on auth type', () => { const request = httpServerMock.createKibanaRequest(); const response = await routeHandler(mockContext, request, kibanaResponseFactory); - expect(response.status).toBe(400); - expect(response.payload).toEqual({ message: 'Authentication type can not be empty' }); + expect(response.status).toBe(204); + expect(response.payload).toBeUndefined(); expect(mockUsageCounter.incrementCounter).toHaveBeenCalledTimes(0); });