From f0de52f964a5166a6f325ffb84b0b9f9b0416d14 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau Date: Mon, 29 Nov 2021 15:01:05 -0500 Subject: [PATCH] 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 (