Skip to content

Commit

Permalink
Merge branch 'main' into 145717
Browse files Browse the repository at this point in the history
  • Loading branch information
yctercero committed Feb 23, 2023
2 parents 9a8a975 + 9a40d6a commit d55e861
Show file tree
Hide file tree
Showing 46 changed files with 1,044 additions and 734 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
'xpack.cloud.is_elastic_staff_owned (boolean)',
'xpack.cloud.trial_end_date (string)',
'xpack.cloud_integrations.chat.chatURL (string)',
'xpack.cloud_integrations.chat.trialBuffer (number)',
// No PII. This is an escape patch to override LaunchDarkly's flag resolution mechanism for testing or quick fix.
'xpack.cloud_integrations.experiments.flag_overrides (record)',
// Commented because it's inside a schema conditional, and the test is not able to resolve it. But it's shared.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
*/

export const GET_CHAT_USER_DATA_ROUTE_PATH = '/internal/cloud/chat_user';
export const DEFAULT_TRIAL_BUFFER = 60;
19 changes: 19 additions & 0 deletions x-pack/plugins/cloud_integrations/cloud_chat/common/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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.
*/

/**
* Returns true if today's date is within the an end date + buffer, false otherwise.
*
* @param endDate The end date of the trial.
* @param buffer The number of days to add to the end date.
* @returns true if today's date is within the an end date + buffer, false otherwise.
*/
export const isTodayInDateWindow = (endDate: Date, buffer: number) => {
const endDateWithBuffer = new Date(endDate);
endDateWithBuffer.setDate(endDateWithBuffer.getDate() + buffer);
return endDateWithBuffer > new Date();
};
24 changes: 20 additions & 4 deletions x-pack/plugins/cloud_integrations/cloud_chat/public/plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ describe('Cloud Chat Plugin', () => {
describe('#setup', () => {
describe('setupChat', () => {
let consoleMock: jest.SpyInstance<void, [message?: any, ...optionalParams: any[]]>;
let newTrialEndDate: Date;

beforeEach(() => {
consoleMock = jest.spyOn(console, 'debug').mockImplementation(() => {});
newTrialEndDate = new Date();
newTrialEndDate.setDate(new Date().getDate() + 14);
});

afterEach(() => {
Expand All @@ -30,12 +33,14 @@ describe('Cloud Chat Plugin', () => {
currentUserProps = {},
isCloudEnabled = true,
failHttp = false,
trialEndDate = newTrialEndDate,
}: {
config?: Partial<CloudChatConfigType>;
securityEnabled?: boolean;
currentUserProps?: Record<string, any>;
isCloudEnabled?: boolean;
failHttp?: boolean;
trialEndDate?: Date;
}) => {
const initContext = coreMock.createPluginInitializerContext(config);

Expand All @@ -60,7 +65,7 @@ describe('Cloud Chat Plugin', () => {
const cloud = cloudMock.createSetup();

plugin.setup(coreSetup, {
cloud: { ...cloud, isCloudEnabled },
cloud: { ...cloud, isCloudEnabled, trialEndDate },
...(securityEnabled ? { security: securitySetup } : {}),
});

Expand All @@ -85,16 +90,27 @@ describe('Cloud Chat Plugin', () => {

it('chatConfig is not retrieved if internal API fails', async () => {
const { coreSetup } = await setupPlugin({
config: { chatURL: 'http://chat.elastic.co' },
config: { chatURL: 'http://chat.elastic.co', trialBuffer: 30 },
failHttp: true,
});
expect(coreSetup.http.get).toHaveBeenCalled();
expect(consoleMock).toHaveBeenCalled();
});

it('chatConfig is retrieved if chat is enabled and url is provided', async () => {
it('chatConfig is not retrieved if chat is enabled and url is provided but trial has expired', async () => {
const date = new Date();
date.setDate(new Date().getDate() - 44);
const { coreSetup } = await setupPlugin({
config: { chatURL: 'http://chat.elastic.co' },
config: { chatURL: 'http://chat.elastic.co', trialBuffer: 30 },
trialEndDate: date,
});
expect(coreSetup.http.get).not.toHaveBeenCalled();
});

it('chatConfig is retrieved if chat is enabled and url is provided and trial is active', async () => {
const { coreSetup } = await setupPlugin({
config: { chatURL: 'http://chat.elastic.co', trialBuffer: 30 },
trialEndDate: new Date(),
});
expect(coreSetup.http.get).toHaveBeenCalled();
});
Expand Down
15 changes: 13 additions & 2 deletions x-pack/plugins/cloud_integrations/cloud_chat/public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { ReplaySubject } from 'rxjs';
import type { GetChatUserDataResponseBody } from '../common/types';
import { GET_CHAT_USER_DATA_ROUTE_PATH } from '../common/constants';
import { ChatConfig, ServicesProvider } from './services';
import { isTodayInDateWindow } from '../common/util';

interface CloudChatSetupDeps {
cloud: CloudSetup;
Expand All @@ -27,6 +28,7 @@ interface SetupChatDeps extends CloudChatSetupDeps {

interface CloudChatConfig {
chatURL?: string;
trialBuffer: number;
}

export class CloudChatPlugin implements Plugin {
Expand Down Expand Up @@ -57,7 +59,16 @@ export class CloudChatPlugin implements Plugin {
public stop() {}

private async setupChat({ cloud, http, security }: SetupChatDeps) {
if (!cloud.isCloudEnabled || !security || !this.config.chatURL) {
const { isCloudEnabled, trialEndDate } = cloud;
const { chatURL, trialBuffer } = this.config;

if (
!security ||
!isCloudEnabled ||
!chatURL ||
!trialEndDate ||
!isTodayInDateWindow(trialEndDate, trialBuffer)
) {
return;
}

Expand All @@ -73,7 +84,7 @@ export class CloudChatPlugin implements Plugin {
}

this.chatConfig$.next({
chatURL: this.config.chatURL,
chatURL,
user: {
email,
id,
Expand Down
4 changes: 4 additions & 0 deletions x-pack/plugins/cloud_integrations/cloud_chat/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,21 @@ import { get, has } from 'lodash';
import { schema, TypeOf } from '@kbn/config-schema';
import { PluginConfigDescriptor } from '@kbn/core/server';

import { DEFAULT_TRIAL_BUFFER } from '../common/constants';

const configSchema = schema.object({
enabled: schema.boolean({ defaultValue: false }),
chatURL: schema.maybe(schema.string()),
chatIdentitySecret: schema.maybe(schema.string()),
trialBuffer: schema.number({ defaultValue: DEFAULT_TRIAL_BUFFER }),
});

export type CloudChatConfigType = TypeOf<typeof configSchema>;

export const config: PluginConfigDescriptor<CloudChatConfigType> = {
exposeToBrowser: {
chatURL: true,
trialBuffer: true,
},
schema: configSchema,
deprecations: () => [
Expand Down
15 changes: 10 additions & 5 deletions x-pack/plugins/cloud_integrations/cloud_chat/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@

import { PluginInitializerContext, CoreSetup, Plugin } from '@kbn/core/server';

import { SecurityPluginSetup } from '@kbn/security-plugin/server';
import { CloudSetup } from '@kbn/cloud-plugin/server';
import type { SecurityPluginSetup } from '@kbn/security-plugin/server';
import type { CloudSetup } from '@kbn/cloud-plugin/server';
import { registerChatRoute } from './routes';
import { CloudChatConfigType } from './config';
import type { CloudChatConfigType } from './config';

interface CloudChatSetupDeps {
cloud: CloudSetup;
Expand All @@ -27,10 +27,15 @@ export class CloudChatPlugin implements Plugin<void, void, CloudChatSetupDeps> {
}

public setup(core: CoreSetup, { cloud, security }: CloudChatSetupDeps) {
if (cloud.isCloudEnabled && this.config.chatIdentitySecret) {
const { chatIdentitySecret, trialBuffer } = this.config;
const { isCloudEnabled, trialEndDate } = cloud;

if (isCloudEnabled && chatIdentitySecret) {
registerChatRoute({
router: core.http.createRouter(),
chatIdentitySecret: this.config.chatIdentitySecret,
chatIdentitySecret,
trialEndDate,
trialBuffer,
security,
isDev: this.isDev,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { registerChatRoute } from './chat';
describe('chat route', () => {
test('do not add the route if security is not enabled', async () => {
const router = httpServiceMock.createRouter();
registerChatRoute({ router, isDev: false, chatIdentitySecret: 'secret' });
registerChatRoute({ router, isDev: false, chatIdentitySecret: 'secret', trialBuffer: 60 });
expect(router.get.mock.calls).toEqual([]);
});

Expand All @@ -28,7 +28,14 @@ describe('chat route', () => {
security.authc.getCurrentUser.mockReturnValueOnce(null);

const router = httpServiceMock.createRouter();
registerChatRoute({ router, security, isDev: false, chatIdentitySecret: 'secret' });
registerChatRoute({
router,
security,
isDev: false,
chatIdentitySecret: 'secret',
trialBuffer: 60,
trialEndDate: new Date(),
});

const [_config, handler] = router.get.mock.calls[0];

Expand All @@ -44,6 +51,79 @@ describe('chat route', () => {
`);
});

test('error if no trial end date specified', async () => {
const security = securityMock.createSetup();
const username = 'user.name';
const email = 'user@elastic.co';

security.authc.getCurrentUser.mockReturnValueOnce({
username,
metadata: {
saml_email: [email],
},
});

const router = httpServiceMock.createRouter();
registerChatRoute({
router,
security,
isDev: false,
chatIdentitySecret: 'secret',
trialBuffer: 2,
});

const [_config, handler] = router.get.mock.calls[0];

await expect(handler({}, httpServerMock.createKibanaRequest(), kibanaResponseFactory)).resolves
.toMatchInlineSnapshot(`
KibanaResponse {
"options": Object {
"body": "Chat can only be started if a trial end date is specified",
},
"payload": "Chat can only be started if a trial end date is specified",
"status": 400,
}
`);
});

test('error if not in trial window', async () => {
const security = securityMock.createSetup();
const username = 'user.name';
const email = 'user@elastic.co';

security.authc.getCurrentUser.mockReturnValueOnce({
username,
metadata: {
saml_email: [email],
},
});

const router = httpServiceMock.createRouter();
const trialEndDate = new Date();
trialEndDate.setDate(trialEndDate.getDate() - 30);
registerChatRoute({
router,
security,
isDev: false,
chatIdentitySecret: 'secret',
trialBuffer: 2,
trialEndDate,
});

const [_config, handler] = router.get.mock.calls[0];

await expect(handler({}, httpServerMock.createKibanaRequest(), kibanaResponseFactory)).resolves
.toMatchInlineSnapshot(`
KibanaResponse {
"options": Object {
"body": "Chat can only be started during trial and trial chat buffer",
},
"payload": "Chat can only be started during trial and trial chat buffer",
"status": 400,
}
`);
});

test('returns user information taken from saml metadata and a token', async () => {
const security = securityMock.createSetup();
const username = 'user.name';
Expand All @@ -57,7 +137,14 @@ describe('chat route', () => {
});

const router = httpServiceMock.createRouter();
registerChatRoute({ router, security, isDev: false, chatIdentitySecret: 'secret' });
registerChatRoute({
router,
security,
isDev: false,
chatIdentitySecret: 'secret',
trialBuffer: 60,
trialEndDate: new Date(),
});
const [_config, handler] = router.get.mock.calls[0];
await expect(handler({}, httpServerMock.createKibanaRequest(), kibanaResponseFactory)).resolves
.toMatchInlineSnapshot(`
Expand Down Expand Up @@ -87,7 +174,14 @@ describe('chat route', () => {
security.authc.getCurrentUser.mockReturnValueOnce({});

const router = httpServiceMock.createRouter();
registerChatRoute({ router, security, isDev: true, chatIdentitySecret: 'secret' });
registerChatRoute({
router,
security,
isDev: true,
chatIdentitySecret: 'secret',
trialBuffer: 60,
trialEndDate: new Date(),
});
const [_config, handler] = router.get.mock.calls[0];
await expect(handler({}, httpServerMock.createKibanaRequest(), kibanaResponseFactory)).resolves
.toMatchInlineSnapshot(`
Expand Down
17 changes: 17 additions & 0 deletions x-pack/plugins/cloud_integrations/cloud_chat/server/routes/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { SecurityPluginSetup, AuthenticatedUser } from '@kbn/security-plugi
import { GET_CHAT_USER_DATA_ROUTE_PATH } from '../../common/constants';
import type { GetChatUserDataResponseBody } from '../../common/types';
import { generateSignedJwt } from '../util/generate_jwt';
import { isTodayInDateWindow } from '../../common/util';

type MetaWithSaml = AuthenticatedUser['metadata'] & {
saml_name: [string];
Expand All @@ -21,11 +22,15 @@ type MetaWithSaml = AuthenticatedUser['metadata'] & {
export const registerChatRoute = ({
router,
chatIdentitySecret,
trialEndDate,
trialBuffer,
security,
isDev,
}: {
router: IRouter;
chatIdentitySecret: string;
trialEndDate?: Date;
trialBuffer: number;
security?: SecurityPluginSetup;
isDev: boolean;
}) => {
Expand Down Expand Up @@ -61,6 +66,18 @@ export const registerChatRoute = ({
});
}

if (!trialEndDate) {
return response.badRequest({
body: 'Chat can only be started if a trial end date is specified',
});
}

if (!trialEndDate || !isTodayInDateWindow(trialEndDate, trialBuffer)) {
return response.badRequest({
body: 'Chat can only be started during trial and trial chat buffer',
});
}

const token = generateSignedJwt(userId, chatIdentitySecret);
const body: GetChatUserDataResponseBody = {
token,
Expand Down

0 comments on commit d55e861

Please sign in to comment.