mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[Response Ops][Connectors] New xpack.actions.email.services.enabled
Kibana setting (#223363)
## Summary Closes #220288 ## Release note New kibana setting `xpack.actions.email.services.enabled` to enable/disable email services for email connector.
This commit is contained in:
parent
028660e4e1
commit
411ab215a5
34 changed files with 608 additions and 63 deletions
|
@ -198,7 +198,8 @@ enabled:
|
||||||
- x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/config.ts
|
- x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/config.ts
|
||||||
- x-pack/test/functional_with_es_ssl/apps/embeddable_alerts_table/config.ts
|
- x-pack/test/functional_with_es_ssl/apps/embeddable_alerts_table/config.ts
|
||||||
- x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/config.ts
|
- x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/config.ts
|
||||||
- x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors/with_aws_ses_kibana_config/config.ts
|
- x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors/with_email_aws_ses_kbn_config/config.ts
|
||||||
|
- x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors/with_email_services_enabled_kbn_config/config.ts
|
||||||
- x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/shared/config.ts
|
- x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/shared/config.ts
|
||||||
- x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors/webhook_disabled_ssl_pfx/config.ts
|
- x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors/webhook_disabled_ssl_pfx/config.ts
|
||||||
- x-pack/test/functional/apps/advanced_settings/config.ts
|
- x-pack/test/functional/apps/advanced_settings/config.ts
|
||||||
|
|
|
@ -150,6 +150,12 @@ $$$action-config-email-domain-allowlist$$$
|
||||||
Data type: `int`
|
Data type: `int`
|
||||||
Default: `465`
|
Default: `465`
|
||||||
|
|
||||||
|
`xpack.actions.email.services.enabled` 
|
||||||
|
: An array of strings indicating all email services that are enabled. Available options are `elastic-cloud`, `google-mail`, `microsoft-outlook`, `amazon-ses`, `microsoft-exchange`, and `other`. If the array is empty, no email services are enabled. The default value is `["*"]`, which enables all email services.
|
||||||
|
|
||||||
|
Data type: `string`
|
||||||
|
Default: `["*"]`
|
||||||
|
|
||||||
`xpack.actions.enableFooterInEmail` 
|
`xpack.actions.enableFooterInEmail` 
|
||||||
: A boolean value indicating that a footer with a relevant link should be added to emails sent as alerting actions.
|
: A boolean value indicating that a footer with a relevant link should be added to emails sent as alerting actions.
|
||||||
|
|
||||||
|
|
|
@ -326,6 +326,28 @@ groups:
|
||||||
ess: all
|
ess: all
|
||||||
# example: |
|
# example: |
|
||||||
|
|
||||||
|
- setting: xpack.actions.email.services.enabled
|
||||||
|
id: action-config-email-services-
|
||||||
|
description: |
|
||||||
|
An array of strings indicating all email services that are enabled. Available options are `elastic-cloud`, `google-mail`, `microsoft-outlook`, `amazon-ses`, `microsoft-exchange`, and `other`. If the array is empty, no email services are enabled. The default value is `["*"]`, which enables all email services.
|
||||||
|
# state: deprecated/hidden/tech-preview
|
||||||
|
# deprecation_details: ""
|
||||||
|
# note: ""
|
||||||
|
# tip: ""
|
||||||
|
# warning: ""
|
||||||
|
# important: ""
|
||||||
|
datatype: enum
|
||||||
|
default: ["*"]
|
||||||
|
# options:
|
||||||
|
# - option:
|
||||||
|
# description: ""
|
||||||
|
# type: static/dynamic
|
||||||
|
applies_to:
|
||||||
|
deployment:
|
||||||
|
self: all
|
||||||
|
ess: all
|
||||||
|
# example: |
|
||||||
|
|
||||||
- setting: xpack.actions.enableFooterInEmail
|
- setting: xpack.actions.enableFooterInEmail
|
||||||
# id:
|
# id:
|
||||||
description: |
|
description: |
|
||||||
|
|
|
@ -214,6 +214,7 @@ kibana_vars=(
|
||||||
xpack.actions.email.domain_allowlist
|
xpack.actions.email.domain_allowlist
|
||||||
xpack.actions.email.services.ses.host
|
xpack.actions.email.services.ses.host
|
||||||
xpack.actions.email.services.ses.port
|
xpack.actions.email.services.ses.port
|
||||||
|
xpack.actions.email.services.enabled
|
||||||
xpack.actions.enableFooterInEmail
|
xpack.actions.enableFooterInEmail
|
||||||
xpack.actions.enabledActionTypes
|
xpack.actions.enabledActionTypes
|
||||||
xpack.actions.maxResponseContentLength
|
xpack.actions.maxResponseContentLength
|
||||||
|
|
|
@ -203,6 +203,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
|
||||||
'vis_type_xy.readOnly (boolean?|never)',
|
'vis_type_xy.readOnly (boolean?|never)',
|
||||||
'vis_type_vega.enableExternalUrls (boolean?)',
|
'vis_type_vega.enableExternalUrls (boolean?)',
|
||||||
'xpack.actions.email.domain_allowlist (array?)',
|
'xpack.actions.email.domain_allowlist (array?)',
|
||||||
|
'xpack.actions.email.services.enabled (array?)',
|
||||||
'xpack.actions.webhook.ssl.pfx.enabled (boolean?)',
|
'xpack.actions.webhook.ssl.pfx.enabled (boolean?)',
|
||||||
'xpack.apm.serviceMapEnabled (boolean?)',
|
'xpack.apm.serviceMapEnabled (boolean?)',
|
||||||
'xpack.apm.ui.enabled (boolean?)',
|
'xpack.apm.ui.enabled (boolean?)',
|
||||||
|
|
|
@ -14,12 +14,16 @@ export interface ActionsPublicPluginSetup {
|
||||||
emails: string[],
|
emails: string[],
|
||||||
options?: ValidateEmailAddressesOptions
|
options?: ValidateEmailAddressesOptions
|
||||||
): ValidatedEmail[];
|
): ValidatedEmail[];
|
||||||
|
enabledEmailServices: string[];
|
||||||
isWebhookSslWithPfxEnabled?: boolean;
|
isWebhookSslWithPfxEnabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Config {
|
export interface Config {
|
||||||
email: {
|
email: {
|
||||||
domain_allowlist: string[];
|
domain_allowlist: string[];
|
||||||
|
services: {
|
||||||
|
enabled: string[];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
webhook: {
|
webhook: {
|
||||||
ssl: {
|
ssl: {
|
||||||
|
@ -32,11 +36,13 @@ export interface Config {
|
||||||
|
|
||||||
export class Plugin implements CorePlugin<ActionsPublicPluginSetup> {
|
export class Plugin implements CorePlugin<ActionsPublicPluginSetup> {
|
||||||
private readonly allowedEmailDomains: string[] | null = null;
|
private readonly allowedEmailDomains: string[] | null = null;
|
||||||
|
private readonly enabledEmailServices: string[];
|
||||||
private readonly webhookSslWithPfxEnabled: boolean;
|
private readonly webhookSslWithPfxEnabled: boolean;
|
||||||
|
|
||||||
constructor(ctx: PluginInitializerContext<Config>) {
|
constructor(ctx: PluginInitializerContext<Config>) {
|
||||||
const config = ctx.config.get();
|
const config = ctx.config.get();
|
||||||
this.allowedEmailDomains = config.email?.domain_allowlist || null;
|
this.allowedEmailDomains = config.email?.domain_allowlist || null;
|
||||||
|
this.enabledEmailServices = Array.from(new Set(config.email?.services?.enabled || ['*']));
|
||||||
this.webhookSslWithPfxEnabled = config.webhook?.ssl.pfx.enabled ?? true;
|
this.webhookSslWithPfxEnabled = config.webhook?.ssl.pfx.enabled ?? true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +50,7 @@ export class Plugin implements CorePlugin<ActionsPublicPluginSetup> {
|
||||||
return {
|
return {
|
||||||
validateEmailAddresses: (emails: string[], options: ValidateEmailAddressesOptions) =>
|
validateEmailAddresses: (emails: string[], options: ValidateEmailAddressesOptions) =>
|
||||||
validateEmails(this.allowedEmailDomains, emails, options),
|
validateEmails(this.allowedEmailDomains, emails, options),
|
||||||
|
enabledEmailServices: this.enabledEmailServices,
|
||||||
isWebhookSslWithPfxEnabled: this.webhookSslWithPfxEnabled,
|
isWebhookSslWithPfxEnabled: this.webhookSslWithPfxEnabled,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ const createActionsConfigMock = () => {
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
getAwsSesConfig: jest.fn().mockReturnValue(null),
|
getAwsSesConfig: jest.fn().mockReturnValue(null),
|
||||||
|
getEnabledEmailServices: jest.fn().mockReturnValue(['*']),
|
||||||
};
|
};
|
||||||
return mocked;
|
return mocked;
|
||||||
};
|
};
|
||||||
|
|
|
@ -633,6 +633,14 @@ describe('getAwsSesConfig()', () => {
|
||||||
expect(acu.getAwsSesConfig()).toEqual(null);
|
expect(acu.getAwsSesConfig()).toEqual(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('returns null when no email.services.ses config set', () => {
|
||||||
|
const acu = getActionsConfigurationUtilities({
|
||||||
|
...defaultActionsConfig,
|
||||||
|
email: { services: {} },
|
||||||
|
});
|
||||||
|
expect(acu.getAwsSesConfig()).toEqual(null);
|
||||||
|
});
|
||||||
|
|
||||||
test('returns config if set', () => {
|
test('returns config if set', () => {
|
||||||
const acu = getActionsConfigurationUtilities({
|
const acu = getActionsConfigurationUtilities({
|
||||||
...defaultActionsConfig,
|
...defaultActionsConfig,
|
||||||
|
@ -652,3 +660,47 @@ describe('getAwsSesConfig()', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getEnabledEmailServices()', () => {
|
||||||
|
test('returns all services when no email config set', () => {
|
||||||
|
const acu = getActionsConfigurationUtilities(defaultActionsConfig);
|
||||||
|
expect(acu.getEnabledEmailServices()).toEqual(['*']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns all services when no email.services config set', () => {
|
||||||
|
const acu = getActionsConfigurationUtilities({ ...defaultActionsConfig, email: {} });
|
||||||
|
expect(acu.getEnabledEmailServices()).toEqual(['*']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns all services when no email.services.enabled config set', () => {
|
||||||
|
const acu = getActionsConfigurationUtilities({
|
||||||
|
...defaultActionsConfig,
|
||||||
|
email: { services: {} },
|
||||||
|
});
|
||||||
|
expect(acu.getEnabledEmailServices()).toEqual(['*']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns only enabled services', () => {
|
||||||
|
const acu = getActionsConfigurationUtilities({
|
||||||
|
...defaultActionsConfig,
|
||||||
|
email: {
|
||||||
|
services: {
|
||||||
|
enabled: ['google-mail', 'microsoft-exchange'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(acu.getEnabledEmailServices()).toEqual(['google-mail', 'microsoft-exchange']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns all services when enabled is set to "*" in config', () => {
|
||||||
|
const acu = getActionsConfigurationUtilities({
|
||||||
|
...defaultActionsConfig,
|
||||||
|
email: {
|
||||||
|
services: {
|
||||||
|
enabled: ['*'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(acu.getEnabledEmailServices()).toEqual(['*']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -63,6 +63,7 @@ export interface ActionsConfigurationUtilities {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
getAwsSesConfig: () => AwsSesConfig;
|
getAwsSesConfig: () => AwsSesConfig;
|
||||||
|
getEnabledEmailServices: () => string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function allowListErrorMessage(field: AllowListingField, value: string) {
|
function allowListErrorMessage(field: AllowListingField, value: string) {
|
||||||
|
@ -243,15 +244,23 @@ export function getActionsConfigurationUtilities(
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getAwsSesConfig: () => {
|
getAwsSesConfig: () => {
|
||||||
if (config.email?.services?.ses.host && config.email?.services?.ses.port) {
|
if (config.email?.services?.ses?.host && config.email?.services?.ses?.port) {
|
||||||
return {
|
return {
|
||||||
host: config.email?.services?.ses.host,
|
host: config.email?.services?.ses?.host,
|
||||||
port: config.email?.services?.ses.port,
|
port: config.email?.services?.ses?.port,
|
||||||
secure: true,
|
secure: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
getEnabledEmailServices() {
|
||||||
|
const emailServices = config.email?.services?.enabled;
|
||||||
|
if (emailServices) {
|
||||||
|
return Array.from(new Set(Array.from(emailServices)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['*'];
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -238,7 +238,7 @@ describe('config validation', () => {
|
||||||
|
|
||||||
config.email = {};
|
config.email = {};
|
||||||
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
|
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
|
||||||
`"[email]: Email configuration requires either domain_allowlist or services.ses to be specified"`
|
`"[email]: email.domain_allowlist or email.services must be defined"`
|
||||||
);
|
);
|
||||||
|
|
||||||
config.email = { domain_allowlist: [] };
|
config.email = { domain_allowlist: [] };
|
||||||
|
@ -285,35 +285,35 @@ describe('config validation', () => {
|
||||||
test('validates empty email config', () => {
|
test('validates empty email config', () => {
|
||||||
config.email = {};
|
config.email = {};
|
||||||
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
|
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
|
||||||
`"[email]: Email configuration requires either domain_allowlist or services.ses to be specified"`
|
`"[email]: email.domain_allowlist or email.services must be defined"`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('validates email config with empty services', () => {
|
test('validates email config with empty services', () => {
|
||||||
config.email = { services: {} };
|
config.email = { services: {} };
|
||||||
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
|
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
|
||||||
`"[email]: Email configuration requires either domain_allowlist or services.ses to be specified"`
|
`"[email.services]: email.services.enabled or email.services.ses must be defined"`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('validates email config with empty ses service', () => {
|
test('validates email config with empty ses service', () => {
|
||||||
config.email = { services: { ses: {} } };
|
config.email = { services: { ses: {} } };
|
||||||
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
|
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
|
||||||
`"[email]: Email configuration requires either domain_allowlist or services.ses to be specified"`
|
`"[email.services.ses.host]: expected value of type [string] but got [undefined]"`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('validates ses config with host only', () => {
|
test('validates ses config with host only', () => {
|
||||||
config.email = { services: { ses: { host: 'ses.host.com' } } };
|
config.email = { services: { ses: { host: 'ses.host.com' } } };
|
||||||
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
|
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
|
||||||
`"[email]: Email configuration requires both services.ses.host and services.ses.port to be specified"`
|
`"[email.services.ses.port]: expected value of type [number] but got [undefined]"`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('validates ses config with port only', () => {
|
test('validates ses config with port only', () => {
|
||||||
config.email = { services: { ses: { port: 1 } } };
|
config.email = { services: { ses: { port: 1 } } };
|
||||||
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
|
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
|
||||||
`"[email]: Email configuration requires both services.ses.host and services.ses.port to be specified"`
|
`"[email.services.ses.host]: expected value of type [string] but got [undefined]"`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -323,6 +323,43 @@ describe('config validation', () => {
|
||||||
expect(result.email?.services?.ses).toEqual({ host: 'ses.host.com', port: 1 });
|
expect(result.email?.services?.ses).toEqual({ host: 'ses.host.com', port: 1 });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('email.services.enabled', () => {
|
||||||
|
const config: Record<string, unknown> = {};
|
||||||
|
test('validates email config with empty enabled services', () => {
|
||||||
|
config.email = { services: { enabled: [] } };
|
||||||
|
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
|
||||||
|
`"[email.services.enabled]: array size is [0], but cannot be smaller than [1]"`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('validates email config with enabled services', () => {
|
||||||
|
config.email = { services: { enabled: ['elastic-cloud', 'amazon-ses'] } };
|
||||||
|
const result = configSchema.validate(config);
|
||||||
|
expect(result.email?.services?.enabled).toEqual(['elastic-cloud', 'amazon-ses']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('validates email config with unexistend service', () => {
|
||||||
|
config.email = { services: { enabled: ['fake-service'] } };
|
||||||
|
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(`
|
||||||
|
"[email.services.enabled.0]: types that failed validation:
|
||||||
|
- [email.services.enabled.0.0]: expected value to equal [google-mail]
|
||||||
|
- [email.services.enabled.0.1]: expected value to equal [microsoft-exchange]
|
||||||
|
- [email.services.enabled.0.2]: expected value to equal [microsoft-outlook]
|
||||||
|
- [email.services.enabled.0.3]: expected value to equal [amazon-ses]
|
||||||
|
- [email.services.enabled.0.4]: expected value to equal [elastic-cloud]
|
||||||
|
- [email.services.enabled.0.5]: expected value to equal [other]
|
||||||
|
- [email.services.enabled.0.6]: expected value to equal [*]"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('validates enabled services but no ses service', () => {
|
||||||
|
config.email = { services: { enabled: ['google-mail', 'amazon-ses'] } };
|
||||||
|
const result = configSchema.validate(config);
|
||||||
|
expect(result.email?.services?.enabled).toEqual(['google-mail', 'amazon-ses']);
|
||||||
|
expect(result.email?.services?.ses).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// object creator that ensures we can create a property named __proto__ on an
|
// object creator that ensures we can create a property named __proto__ on an
|
||||||
|
|
|
@ -125,22 +125,43 @@ export const configSchema = schema.object({
|
||||||
{
|
{
|
||||||
domain_allowlist: schema.maybe(schema.arrayOf(schema.string())),
|
domain_allowlist: schema.maybe(schema.arrayOf(schema.string())),
|
||||||
services: schema.maybe(
|
services: schema.maybe(
|
||||||
schema.object({
|
schema.object(
|
||||||
ses: schema.object({
|
{
|
||||||
host: schema.maybe(schema.string({ minLength: 1 })),
|
enabled: schema.maybe(
|
||||||
port: schema.maybe(schema.number({ min: 1, max: 65535 })),
|
schema.arrayOf(
|
||||||
}),
|
schema.oneOf([
|
||||||
})
|
schema.literal('google-mail'),
|
||||||
|
schema.literal('microsoft-exchange'),
|
||||||
|
schema.literal('microsoft-outlook'),
|
||||||
|
schema.literal('amazon-ses'),
|
||||||
|
schema.literal('elastic-cloud'),
|
||||||
|
schema.literal('other'),
|
||||||
|
schema.literal('*'),
|
||||||
|
]),
|
||||||
|
{ minSize: 1 }
|
||||||
|
)
|
||||||
|
),
|
||||||
|
ses: schema.maybe(
|
||||||
|
schema.object({
|
||||||
|
host: schema.string({ minLength: 1 }),
|
||||||
|
port: schema.number({ min: 1, max: 65535 }),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
validate: (obj) => {
|
||||||
|
if (obj && Object.keys(obj).length === 0) {
|
||||||
|
return 'email.services.enabled or email.services.ses must be defined';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
validate: (obj) => {
|
validate: (obj) => {
|
||||||
if (!obj.domain_allowlist && !obj.services?.ses.host && !obj.services?.ses.port) {
|
if (obj && Object.keys(obj).length === 0) {
|
||||||
return 'Email configuration requires either domain_allowlist or services.ses to be specified';
|
return 'email.domain_allowlist or email.services must be defined';
|
||||||
}
|
|
||||||
|
|
||||||
if (obj.services?.ses && (!obj.services.ses.host || !obj.services.ses.port)) {
|
|
||||||
return 'Email configuration requires both services.ses.host and services.ses.port to be specified';
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ export type { ServiceParams } from './sub_action_framework/types';
|
||||||
export const config: PluginConfigDescriptor<ActionsConfig> = {
|
export const config: PluginConfigDescriptor<ActionsConfig> = {
|
||||||
schema: configSchema,
|
schema: configSchema,
|
||||||
exposeToBrowser: {
|
exposeToBrowser: {
|
||||||
email: { domain_allowlist: true },
|
email: { domain_allowlist: true, services: { enabled: true } },
|
||||||
webhook: { ssl: { pfx: { enabled: true } } },
|
webhook: { ssl: { pfx: { enabled: true } } },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const serviceParamValueToKbnSettingMap = {
|
||||||
|
gmail: 'google-mail',
|
||||||
|
outlook365: 'microsoft-outlook',
|
||||||
|
ses: 'amazon-ses',
|
||||||
|
elastic_cloud: 'elastic-cloud',
|
||||||
|
exchange_server: 'microsoft-exchange',
|
||||||
|
other: 'other',
|
||||||
|
} as const;
|
|
@ -8,7 +8,7 @@
|
||||||
import { TypeRegistry } from '@kbn/triggers-actions-ui-plugin/public/application/type_registry';
|
import { TypeRegistry } from '@kbn/triggers-actions-ui-plugin/public/application/type_registry';
|
||||||
import { registerConnectorTypes } from '..';
|
import { registerConnectorTypes } from '..';
|
||||||
import type { ActionTypeModel as ConnectorTypeModel } from '@kbn/triggers-actions-ui-plugin/public/types';
|
import type { ActionTypeModel as ConnectorTypeModel } from '@kbn/triggers-actions-ui-plugin/public/types';
|
||||||
import { getEmailServices } from './email';
|
import { emailServices, getEmailServices } from './email';
|
||||||
import {
|
import {
|
||||||
ValidatedEmail,
|
ValidatedEmail,
|
||||||
InvalidEmailReason,
|
InvalidEmailReason,
|
||||||
|
@ -17,6 +17,7 @@ import {
|
||||||
} from '@kbn/actions-plugin/common';
|
} from '@kbn/actions-plugin/common';
|
||||||
import { experimentalFeaturesMock } from '../../mocks';
|
import { experimentalFeaturesMock } from '../../mocks';
|
||||||
import { ExperimentalFeaturesService } from '../../common/experimental_features_service';
|
import { ExperimentalFeaturesService } from '../../common/experimental_features_service';
|
||||||
|
import { serviceParamValueToKbnSettingMap } from '../../../common/email/constants';
|
||||||
|
|
||||||
const CONNECTOR_TYPE_ID = '.email';
|
const CONNECTOR_TYPE_ID = '.email';
|
||||||
let connectorTypeModel: ConnectorTypeModel;
|
let connectorTypeModel: ConnectorTypeModel;
|
||||||
|
@ -65,14 +66,65 @@ describe('connectorTypeRegistry.get() works', () => {
|
||||||
|
|
||||||
describe('getEmailServices', () => {
|
describe('getEmailServices', () => {
|
||||||
test('should return elastic cloud service if isCloudEnabled is true', () => {
|
test('should return elastic cloud service if isCloudEnabled is true', () => {
|
||||||
const services = getEmailServices(true);
|
const services = getEmailServices(true, ['*']);
|
||||||
expect(services.find((service) => service.value === 'elastic_cloud')).toBeTruthy();
|
expect(services.find((service) => service.value === 'elastic_cloud')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should not return elastic cloud service if isCloudEnabled is false', () => {
|
test('should not return elastic cloud service if isCloudEnabled is false', () => {
|
||||||
const services = getEmailServices(false);
|
const services = getEmailServices(false, ['*']);
|
||||||
expect(services.find((service) => service.value === 'elastic_cloud')).toBeFalsy();
|
expect(services.find((service) => service.value === 'elastic_cloud')).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should return all services if enabledEmailsServices is *', () => {
|
||||||
|
const services = getEmailServices(true, ['*']);
|
||||||
|
expect(services).toEqual(emailServices);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return only specified services if enabledEmailsServices is not empty', () => {
|
||||||
|
const services = getEmailServices(true, [
|
||||||
|
serviceParamValueToKbnSettingMap.gmail,
|
||||||
|
serviceParamValueToKbnSettingMap.outlook365,
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(services).toEqual([
|
||||||
|
{
|
||||||
|
['kbn-setting-value']: 'google-mail',
|
||||||
|
text: 'Gmail',
|
||||||
|
value: 'gmail',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
['kbn-setting-value']: 'microsoft-outlook',
|
||||||
|
text: 'Outlook',
|
||||||
|
value: 'outlook365',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return enabled services and the current service if specified', () => {
|
||||||
|
const services = getEmailServices(
|
||||||
|
true,
|
||||||
|
[serviceParamValueToKbnSettingMap.gmail, serviceParamValueToKbnSettingMap.outlook365],
|
||||||
|
serviceParamValueToKbnSettingMap.other
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(services).toEqual([
|
||||||
|
{
|
||||||
|
['kbn-setting-value']: 'google-mail',
|
||||||
|
text: 'Gmail',
|
||||||
|
value: 'gmail',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
['kbn-setting-value']: 'microsoft-outlook',
|
||||||
|
text: 'Outlook',
|
||||||
|
value: 'outlook365',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
['kbn-setting-value']: 'other',
|
||||||
|
text: 'Other',
|
||||||
|
value: 'other',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('action params validation', () => {
|
describe('action params validation', () => {
|
||||||
|
|
|
@ -16,50 +16,76 @@ import type {
|
||||||
} from '@kbn/triggers-actions-ui-plugin/public/types';
|
} from '@kbn/triggers-actions-ui-plugin/public/types';
|
||||||
import { EmailActionParams, EmailConfig, EmailSecrets } from '../types';
|
import { EmailActionParams, EmailConfig, EmailSecrets } from '../types';
|
||||||
import { RegistrationServices } from '..';
|
import { RegistrationServices } from '..';
|
||||||
|
import { serviceParamValueToKbnSettingMap as emailKbnSettings } from '../../../common/email/constants';
|
||||||
|
|
||||||
const emailServices: EuiSelectOption[] = [
|
export const emailServices: Array<EuiSelectOption & { 'kbn-setting-value': string }> = [
|
||||||
{
|
{
|
||||||
text: i18n.translate('xpack.stackConnectors.components.email.gmailServerTypeLabel', {
|
text: i18n.translate('xpack.stackConnectors.components.email.gmailServerTypeLabel', {
|
||||||
defaultMessage: 'Gmail',
|
defaultMessage: 'Gmail',
|
||||||
}),
|
}),
|
||||||
value: 'gmail',
|
value: 'gmail',
|
||||||
|
['kbn-setting-value']: emailKbnSettings.gmail,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: i18n.translate('xpack.stackConnectors.components.email.outlookServerTypeLabel', {
|
text: i18n.translate('xpack.stackConnectors.components.email.outlookServerTypeLabel', {
|
||||||
defaultMessage: 'Outlook',
|
defaultMessage: 'Outlook',
|
||||||
}),
|
}),
|
||||||
value: 'outlook365',
|
value: 'outlook365',
|
||||||
|
['kbn-setting-value']: emailKbnSettings.outlook365,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: i18n.translate('xpack.stackConnectors.components.email.amazonSesServerTypeLabel', {
|
text: i18n.translate('xpack.stackConnectors.components.email.amazonSesServerTypeLabel', {
|
||||||
defaultMessage: 'Amazon SES',
|
defaultMessage: 'Amazon SES',
|
||||||
}),
|
}),
|
||||||
value: 'ses',
|
value: 'ses',
|
||||||
|
['kbn-setting-value']: emailKbnSettings.ses,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: i18n.translate('xpack.stackConnectors.components.email.elasticCloudServerTypeLabel', {
|
text: i18n.translate('xpack.stackConnectors.components.email.elasticCloudServerTypeLabel', {
|
||||||
defaultMessage: 'Elastic Cloud',
|
defaultMessage: 'Elastic Cloud',
|
||||||
}),
|
}),
|
||||||
value: 'elastic_cloud',
|
value: 'elastic_cloud',
|
||||||
|
['kbn-setting-value']: emailKbnSettings.elastic_cloud,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: i18n.translate('xpack.stackConnectors.components.email.exchangeServerTypeLabel', {
|
text: i18n.translate('xpack.stackConnectors.components.email.exchangeServerTypeLabel', {
|
||||||
defaultMessage: 'MS Exchange Server',
|
defaultMessage: 'MS Exchange Server',
|
||||||
}),
|
}),
|
||||||
value: 'exchange_server',
|
value: 'exchange_server',
|
||||||
|
['kbn-setting-value']: emailKbnSettings.exchange_server,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: i18n.translate('xpack.stackConnectors.components.email.otherServerTypeLabel', {
|
text: i18n.translate('xpack.stackConnectors.components.email.otherServerTypeLabel', {
|
||||||
defaultMessage: 'Other',
|
defaultMessage: 'Other',
|
||||||
}),
|
}),
|
||||||
value: 'other',
|
value: 'other',
|
||||||
|
['kbn-setting-value']: emailKbnSettings.other,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export function getEmailServices(isCloudEnabled: boolean) {
|
// Return the current service regardless of its enabled state to allow users to:
|
||||||
return isCloudEnabled
|
// 1. View the current service in the dropdown UI
|
||||||
|
// 2. Update the service configuration if needed
|
||||||
|
// Note: The connector update endpoint will reject updates where the service
|
||||||
|
// remains unchanged but is disabled.
|
||||||
|
export function getEmailServices(
|
||||||
|
isCloudEnabled: boolean,
|
||||||
|
enabledEmailsServices: string[],
|
||||||
|
currentService?: string
|
||||||
|
): Array<EuiSelectOption & { 'kbn-setting-value': string }> {
|
||||||
|
const allEmailServices = isCloudEnabled
|
||||||
? emailServices
|
? emailServices
|
||||||
: emailServices.filter((service) => service.value !== 'elastic_cloud');
|
: emailServices.filter((service) => service.value !== 'elastic_cloud');
|
||||||
|
|
||||||
|
if (enabledEmailsServices.includes('*')) {
|
||||||
|
return allEmailServices;
|
||||||
|
}
|
||||||
|
|
||||||
|
return allEmailServices.filter(
|
||||||
|
(service) =>
|
||||||
|
service.value === currentService ||
|
||||||
|
enabledEmailsServices.includes(service['kbn-setting-value'])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getConnectorType(
|
export function getConnectorType(
|
||||||
|
|
|
@ -7,22 +7,34 @@
|
||||||
|
|
||||||
import React, { Suspense } from 'react';
|
import React, { Suspense } from 'react';
|
||||||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||||
import { act } from '@testing-library/react';
|
import { act, screen, within } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { useKibana } from '@kbn/triggers-actions-ui-plugin/public';
|
import { useKibana } from '@kbn/triggers-actions-ui-plugin/public';
|
||||||
import EmailActionConnectorFields from './email_connector';
|
import EmailActionConnectorFields from './email_connector';
|
||||||
import * as hooks from './use_email_config';
|
|
||||||
import {
|
import {
|
||||||
AppMockRenderer,
|
AppMockRenderer,
|
||||||
ConnectorFormTestProvider,
|
ConnectorFormTestProvider,
|
||||||
createAppMockRenderer,
|
createAppMockRenderer,
|
||||||
waitForComponentToUpdate,
|
waitForComponentToUpdate,
|
||||||
} from '../lib/test_utils';
|
} from '../lib/test_utils';
|
||||||
|
import { getServiceConfig } from './api';
|
||||||
|
|
||||||
jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana');
|
jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana');
|
||||||
const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;
|
const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;
|
||||||
|
|
||||||
|
jest.mock('./api', () => {
|
||||||
|
return {
|
||||||
|
getServiceConfig: jest.fn(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
describe('EmailActionConnectorFields', () => {
|
describe('EmailActionConnectorFields', () => {
|
||||||
|
const enabledEmailServices = ['*'];
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
test('all connector fields are rendered', async () => {
|
test('all connector fields are rendered', async () => {
|
||||||
const actionConnector = {
|
const actionConnector = {
|
||||||
secrets: {
|
secrets: {
|
||||||
|
@ -170,12 +182,11 @@ describe('EmailActionConnectorFields', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('host, port and secure fields should be disabled when service field is set to well known service', async () => {
|
test('host, port and secure fields should be disabled when service field is set to well known service', async () => {
|
||||||
const getEmailServiceConfig = jest
|
(getServiceConfig as jest.Mock).mockResolvedValue({
|
||||||
.fn()
|
host: 'https://example.com',
|
||||||
.mockResolvedValue({ host: 'https://example.com', port: 80, secure: false });
|
port: 80,
|
||||||
jest
|
secure: false,
|
||||||
.spyOn(hooks, 'useEmailConfig')
|
});
|
||||||
.mockImplementation(() => ({ isLoading: false, getEmailServiceConfig }));
|
|
||||||
|
|
||||||
const actionConnector = {
|
const actionConnector = {
|
||||||
secrets: {
|
secrets: {
|
||||||
|
@ -214,12 +225,11 @@ describe('EmailActionConnectorFields', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('host, port and secure fields should not be disabled when service field is set to other', async () => {
|
test('host, port and secure fields should not be disabled when service field is set to other', async () => {
|
||||||
const getEmailServiceConfig = jest
|
(getServiceConfig as jest.Mock).mockResolvedValue({
|
||||||
.fn()
|
host: 'https://example.com',
|
||||||
.mockResolvedValue({ host: 'https://example.com', port: 80, secure: false });
|
port: 80,
|
||||||
jest
|
secure: false,
|
||||||
.spyOn(hooks, 'useEmailConfig')
|
});
|
||||||
.mockImplementation(() => ({ isLoading: false, getEmailServiceConfig }));
|
|
||||||
|
|
||||||
const actionConnector = {
|
const actionConnector = {
|
||||||
secrets: {
|
secrets: {
|
||||||
|
@ -292,7 +302,7 @@ describe('EmailActionConnectorFields', () => {
|
||||||
<ConnectorFormTestProvider
|
<ConnectorFormTestProvider
|
||||||
connector={actionConnector}
|
connector={actionConnector}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
connectorServices={{ validateEmailAddresses }}
|
connectorServices={{ validateEmailAddresses, enabledEmailServices }}
|
||||||
>
|
>
|
||||||
<EmailActionConnectorFields
|
<EmailActionConnectorFields
|
||||||
readOnly={false}
|
readOnly={false}
|
||||||
|
@ -356,7 +366,7 @@ describe('EmailActionConnectorFields', () => {
|
||||||
<ConnectorFormTestProvider
|
<ConnectorFormTestProvider
|
||||||
connector={actionConnector}
|
connector={actionConnector}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
connectorServices={{ validateEmailAddresses }}
|
connectorServices={{ validateEmailAddresses, enabledEmailServices }}
|
||||||
>
|
>
|
||||||
<EmailActionConnectorFields
|
<EmailActionConnectorFields
|
||||||
readOnly={false}
|
readOnly={false}
|
||||||
|
@ -415,7 +425,7 @@ describe('EmailActionConnectorFields', () => {
|
||||||
<ConnectorFormTestProvider
|
<ConnectorFormTestProvider
|
||||||
connector={actionConnector}
|
connector={actionConnector}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
connectorServices={{ validateEmailAddresses }}
|
connectorServices={{ validateEmailAddresses, enabledEmailServices }}
|
||||||
>
|
>
|
||||||
<EmailActionConnectorFields
|
<EmailActionConnectorFields
|
||||||
readOnly={false}
|
readOnly={false}
|
||||||
|
@ -463,7 +473,7 @@ describe('EmailActionConnectorFields', () => {
|
||||||
<ConnectorFormTestProvider
|
<ConnectorFormTestProvider
|
||||||
connector={actionConnector}
|
connector={actionConnector}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
connectorServices={{ validateEmailAddresses }}
|
connectorServices={{ validateEmailAddresses, enabledEmailServices }}
|
||||||
>
|
>
|
||||||
<EmailActionConnectorFields
|
<EmailActionConnectorFields
|
||||||
readOnly={false}
|
readOnly={false}
|
||||||
|
@ -509,7 +519,7 @@ describe('EmailActionConnectorFields', () => {
|
||||||
<ConnectorFormTestProvider
|
<ConnectorFormTestProvider
|
||||||
connector={actionConnector}
|
connector={actionConnector}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
connectorServices={{ validateEmailAddresses }}
|
connectorServices={{ validateEmailAddresses, enabledEmailServices }}
|
||||||
>
|
>
|
||||||
<EmailActionConnectorFields
|
<EmailActionConnectorFields
|
||||||
readOnly={false}
|
readOnly={false}
|
||||||
|
@ -554,7 +564,7 @@ describe('EmailActionConnectorFields', () => {
|
||||||
<ConnectorFormTestProvider
|
<ConnectorFormTestProvider
|
||||||
connector={actionConnector}
|
connector={actionConnector}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
connectorServices={{ validateEmailAddresses }}
|
connectorServices={{ validateEmailAddresses, enabledEmailServices }}
|
||||||
>
|
>
|
||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
<EmailActionConnectorFields
|
<EmailActionConnectorFields
|
||||||
|
@ -603,7 +613,7 @@ describe('EmailActionConnectorFields', () => {
|
||||||
<ConnectorFormTestProvider
|
<ConnectorFormTestProvider
|
||||||
connector={actionConnector}
|
connector={actionConnector}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
connectorServices={{ validateEmailAddresses }}
|
connectorServices={{ validateEmailAddresses, enabledEmailServices }}
|
||||||
>
|
>
|
||||||
<EmailActionConnectorFields
|
<EmailActionConnectorFields
|
||||||
readOnly={false}
|
readOnly={false}
|
||||||
|
@ -649,7 +659,7 @@ describe('EmailActionConnectorFields', () => {
|
||||||
<ConnectorFormTestProvider
|
<ConnectorFormTestProvider
|
||||||
connector={actionConnector}
|
connector={actionConnector}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
connectorServices={{ validateEmailAddresses }}
|
connectorServices={{ validateEmailAddresses, enabledEmailServices }}
|
||||||
>
|
>
|
||||||
<EmailActionConnectorFields
|
<EmailActionConnectorFields
|
||||||
readOnly={false}
|
readOnly={false}
|
||||||
|
@ -689,3 +699,102 @@ describe('EmailActionConnectorFields', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when not all email services are enabled', () => {
|
||||||
|
const enabledEmailServices = ['amazon-ses', 'other', 'microsoft-exchange'];
|
||||||
|
let appMockRenderer: AppMockRenderer;
|
||||||
|
const onSubmit = jest.fn();
|
||||||
|
const validateEmailAddresses = jest.fn();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
appMockRenderer = createAppMockRenderer();
|
||||||
|
validateEmailAddresses.mockReturnValue([{ valid: true }]);
|
||||||
|
(getServiceConfig as jest.Mock).mockResolvedValue({
|
||||||
|
host: 'https://example.com',
|
||||||
|
port: 2255,
|
||||||
|
secure: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('only allows enabled services to be selected only', async () => {
|
||||||
|
const actionConnector = {
|
||||||
|
secrets: {
|
||||||
|
user: 'user',
|
||||||
|
password: 'pass',
|
||||||
|
},
|
||||||
|
id: 'test',
|
||||||
|
actionTypeId: '.email',
|
||||||
|
name: 'email',
|
||||||
|
config: {},
|
||||||
|
isDeprecated: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
appMockRenderer.render(
|
||||||
|
<ConnectorFormTestProvider
|
||||||
|
connector={actionConnector}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
connectorServices={{ validateEmailAddresses, enabledEmailServices }}
|
||||||
|
>
|
||||||
|
<EmailActionConnectorFields
|
||||||
|
readOnly={false}
|
||||||
|
isEdit={false}
|
||||||
|
registerPreSubmitValidator={() => {}}
|
||||||
|
/>
|
||||||
|
</ConnectorFormTestProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
const emailServiceSelect = screen.getByTestId('emailServiceSelectInput') as HTMLSelectElement;
|
||||||
|
|
||||||
|
const options = within(emailServiceSelect).getAllByRole('option');
|
||||||
|
expect(options).toHaveLength(3);
|
||||||
|
expect(options[0].textContent).toBe('Amazon SES');
|
||||||
|
expect(options[1].textContent).toBe('MS Exchange Server');
|
||||||
|
expect(options[2].textContent).toBe('Other');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds the current connector service to the service list even if not enabled', async () => {
|
||||||
|
const actionConnector = {
|
||||||
|
secrets: {
|
||||||
|
user: 'user',
|
||||||
|
password: 'pass',
|
||||||
|
},
|
||||||
|
id: 'test',
|
||||||
|
actionTypeId: '.email',
|
||||||
|
name: 'email',
|
||||||
|
config: {
|
||||||
|
from: 'test@test.com',
|
||||||
|
test: 'test',
|
||||||
|
service: 'gmail', // not enabled
|
||||||
|
secure: true,
|
||||||
|
},
|
||||||
|
isDeprecated: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
appMockRenderer.render(
|
||||||
|
<ConnectorFormTestProvider
|
||||||
|
connector={actionConnector}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
connectorServices={{ validateEmailAddresses, enabledEmailServices }}
|
||||||
|
>
|
||||||
|
<EmailActionConnectorFields
|
||||||
|
readOnly={false}
|
||||||
|
isEdit={false}
|
||||||
|
registerPreSubmitValidator={() => {}}
|
||||||
|
/>
|
||||||
|
</ConnectorFormTestProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
const emailServiceSelect = screen.getByTestId('emailServiceSelectInput') as HTMLSelectElement;
|
||||||
|
|
||||||
|
const options = within(emailServiceSelect).getAllByRole('option');
|
||||||
|
expect(options).toHaveLength(4);
|
||||||
|
expect(options[0].textContent).toBe('Gmail');
|
||||||
|
expect(options[1].textContent).toBe('Amazon SES');
|
||||||
|
expect(options[2].textContent).toBe('MS Exchange Server');
|
||||||
|
expect(options[3].textContent).toBe('Other');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { lazy, useEffect, useMemo } from 'react';
|
import React, { lazy, useEffect, useMemo, useRef } from 'react';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import { EuiFlexItem, EuiFlexGroup, EuiTitle, EuiSpacer } from '@elastic/eui';
|
import { EuiFlexItem, EuiFlexGroup, EuiTitle, EuiSpacer } from '@elastic/eui';
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
|
@ -102,7 +102,7 @@ export const EmailActionConnectorFields: React.FunctionComponent<ActionConnector
|
||||||
notifications: { toasts },
|
notifications: { toasts },
|
||||||
} = useKibana().services;
|
} = useKibana().services;
|
||||||
const {
|
const {
|
||||||
services: { validateEmailAddresses },
|
services: { validateEmailAddresses, enabledEmailServices },
|
||||||
} = useConnectorContext();
|
} = useConnectorContext();
|
||||||
|
|
||||||
const form = useFormContext();
|
const form = useFormContext();
|
||||||
|
@ -119,6 +119,15 @@ export const EmailActionConnectorFields: React.FunctionComponent<ActionConnector
|
||||||
const { service = null, hasAuth = false } = config ?? {};
|
const { service = null, hasAuth = false } = config ?? {};
|
||||||
const disableServiceConfig = shouldDisableEmailConfiguration(service);
|
const disableServiceConfig = shouldDisableEmailConfiguration(service);
|
||||||
const { isLoading, getEmailServiceConfig } = useEmailConfig({ http, toasts });
|
const { isLoading, getEmailServiceConfig } = useEmailConfig({ http, toasts });
|
||||||
|
const initialService = useRef(service);
|
||||||
|
if (!initialService.current && service) {
|
||||||
|
initialService.current = service;
|
||||||
|
}
|
||||||
|
const availableEmailServices = getEmailServices(
|
||||||
|
isCloud,
|
||||||
|
enabledEmailServices,
|
||||||
|
initialService.current
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchConfig() {
|
async function fetchConfig() {
|
||||||
|
@ -173,7 +182,7 @@ export const EmailActionConnectorFields: React.FunctionComponent<ActionConnector
|
||||||
componentProps={{
|
componentProps={{
|
||||||
euiFieldProps: {
|
euiFieldProps: {
|
||||||
'data-test-subj': 'emailServiceSelectInput',
|
'data-test-subj': 'emailServiceSelectInput',
|
||||||
options: getEmailServices(isCloud),
|
options: availableEmailServices,
|
||||||
fullWidth: true,
|
fullWidth: true,
|
||||||
hasNoInitialSelection: true,
|
hasNoInitialSelection: true,
|
||||||
disabled: readOnly || isLoading,
|
disabled: readOnly || isLoading,
|
||||||
|
|
|
@ -74,6 +74,7 @@ const FormTestProviderComponent: React.FC<FormTestProviderProps> = ({
|
||||||
connectorServices = {
|
connectorServices = {
|
||||||
validateEmailAddresses: jest.fn(),
|
validateEmailAddresses: jest.fn(),
|
||||||
isWebhookSslWithPfxEnabled: true,
|
isWebhookSslWithPfxEnabled: true,
|
||||||
|
enabledEmailServices: ['*'],
|
||||||
},
|
},
|
||||||
}) => {
|
}) => {
|
||||||
const { form } = useForm({ defaultValue });
|
const { form } = useForm({ defaultValue });
|
||||||
|
|
|
@ -34,6 +34,7 @@ import { getConnectorType } from '.';
|
||||||
import type { ValidateEmailAddressesOptions } from '@kbn/actions-plugin/common';
|
import type { ValidateEmailAddressesOptions } from '@kbn/actions-plugin/common';
|
||||||
import { ActionExecutionSourceType } from '@kbn/actions-plugin/server/types';
|
import { ActionExecutionSourceType } from '@kbn/actions-plugin/server/types';
|
||||||
import { AdditionalEmailServices } from '../../../common';
|
import { AdditionalEmailServices } from '../../../common';
|
||||||
|
import { serviceParamValueToKbnSettingMap } from '../../../common/email/constants';
|
||||||
|
|
||||||
const sendEmailMock = sendEmail as jest.Mock;
|
const sendEmailMock = sendEmail as jest.Mock;
|
||||||
|
|
||||||
|
@ -503,6 +504,75 @@ describe('params validation', () => {
|
||||||
);
|
);
|
||||||
}).not.toThrowError();
|
}).not.toThrowError();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('error when using a service that is not enabled', async () => {
|
||||||
|
const configUtils = actionsConfigMock.create();
|
||||||
|
configUtils.getEnabledEmailServices = jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue([
|
||||||
|
serviceParamValueToKbnSettingMap.gmail,
|
||||||
|
serviceParamValueToKbnSettingMap.elastic_cloud,
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
validateConfig(
|
||||||
|
connectorType,
|
||||||
|
{
|
||||||
|
service: 'other',
|
||||||
|
from: 'bob@example.com',
|
||||||
|
host: 'wrong-host',
|
||||||
|
port: 123,
|
||||||
|
secure: true,
|
||||||
|
hasAuth: true,
|
||||||
|
},
|
||||||
|
{ configurationUtilities: configUtils }
|
||||||
|
)
|
||||||
|
).toThrowErrorMatchingInlineSnapshot(
|
||||||
|
`"error validating action type config: [service]: \\"other\\" is not in the list of enabled email services: google-mail,elastic-cloud"`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('no error using enabled services = *', async () => {
|
||||||
|
const configUtils = actionsConfigMock.create();
|
||||||
|
configUtils.getEnabledEmailServices = jest.fn().mockReturnValue(['*']);
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
validateConfig(
|
||||||
|
connectorType,
|
||||||
|
{
|
||||||
|
service: 'other',
|
||||||
|
from: 'bob@example.com',
|
||||||
|
host: 'wrong-host',
|
||||||
|
port: 123,
|
||||||
|
secure: true,
|
||||||
|
hasAuth: true,
|
||||||
|
},
|
||||||
|
{ configurationUtilities: configUtils }
|
||||||
|
)
|
||||||
|
).not.toThrowError();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not throw when fetching service enabled in config', () => {
|
||||||
|
const configUtils = actionsConfigMock.create();
|
||||||
|
configUtils.getEnabledEmailServices = jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue([serviceParamValueToKbnSettingMap.elastic_cloud]);
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
validateConfig(
|
||||||
|
connectorType,
|
||||||
|
{
|
||||||
|
service: 'elastic_cloud',
|
||||||
|
from: 'bob@example.com',
|
||||||
|
host: 'dockerhost',
|
||||||
|
port: 10025,
|
||||||
|
secure: false,
|
||||||
|
hasAuth: false,
|
||||||
|
},
|
||||||
|
{ configurationUtilities: configUtils }
|
||||||
|
)
|
||||||
|
).not.toThrowError();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('execute()', () => {
|
describe('execute()', () => {
|
||||||
|
|
|
@ -34,6 +34,7 @@ import { AdditionalEmailServices } from '../../../common';
|
||||||
import type { SendEmailOptions, Transport } from './send_email';
|
import type { SendEmailOptions, Transport } from './send_email';
|
||||||
import { sendEmail, JSON_TRANSPORT_SERVICE } from './send_email';
|
import { sendEmail, JSON_TRANSPORT_SERVICE } from './send_email';
|
||||||
import { portSchema } from '../lib/schemas';
|
import { portSchema } from '../lib/schemas';
|
||||||
|
import { serviceParamValueToKbnSettingMap as emailKbnSettings } from '../../../common/email/constants';
|
||||||
|
|
||||||
export type EmailConnectorType = ConnectorType<
|
export type EmailConnectorType = ConnectorType<
|
||||||
ConnectorTypeConfigType,
|
ConnectorTypeConfigType,
|
||||||
|
@ -82,6 +83,20 @@ function validateConfig(
|
||||||
const config = configObject;
|
const config = configObject;
|
||||||
const { configurationUtilities } = validatorServices;
|
const { configurationUtilities } = validatorServices;
|
||||||
const awsSesConfig = configurationUtilities.getAwsSesConfig();
|
const awsSesConfig = configurationUtilities.getAwsSesConfig();
|
||||||
|
const enabledServices = configurationUtilities.getEnabledEmailServices();
|
||||||
|
|
||||||
|
const serviceKey = config.service as keyof typeof emailKbnSettings;
|
||||||
|
if (
|
||||||
|
!enabledServices.includes('*') &&
|
||||||
|
config.service in emailKbnSettings &&
|
||||||
|
!enabledServices.includes(emailKbnSettings[serviceKey])
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`[service]: "${
|
||||||
|
emailKbnSettings[serviceKey]
|
||||||
|
}" is not in the list of enabled email services: ${enabledServices.join(',')}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const emails = [config.from];
|
const emails = [config.from];
|
||||||
const invalidEmailsMessage = configurationUtilities.validateEmailAddresses(emails);
|
const invalidEmailsMessage = configurationUtilities.validateEmailAddresses(emails);
|
||||||
|
|
|
@ -24,7 +24,7 @@ const FormTestProviderComponent: React.FC<FormTestProviderProps> = ({
|
||||||
children,
|
children,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
connectorServices = { validateEmailAddresses: jest.fn() },
|
connectorServices = { validateEmailAddresses: jest.fn(), enabledEmailServices: ['*'] },
|
||||||
}) => {
|
}) => {
|
||||||
const { form } = useForm({ defaultValue });
|
const { form } = useForm({ defaultValue });
|
||||||
const { submit } = form;
|
const { submit } = form;
|
||||||
|
|
|
@ -90,11 +90,15 @@ export const App = ({ deps }: { deps: TriggersAndActionsUiServices }) => {
|
||||||
|
|
||||||
export const AppWithoutRouter = ({ sectionsRegex }: { sectionsRegex: string }) => {
|
export const AppWithoutRouter = ({ sectionsRegex }: { sectionsRegex: string }) => {
|
||||||
const {
|
const {
|
||||||
actions: { validateEmailAddresses, isWebhookSslWithPfxEnabled },
|
actions: { validateEmailAddresses, enabledEmailServices, isWebhookSslWithPfxEnabled },
|
||||||
} = useKibana().services;
|
} = useKibana().services;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConnectorProvider value={{ services: { validateEmailAddresses, isWebhookSslWithPfxEnabled } }}>
|
<ConnectorProvider
|
||||||
|
value={{
|
||||||
|
services: { validateEmailAddresses, enabledEmailServices, isWebhookSslWithPfxEnabled },
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route
|
||||||
path={`/:section(${sectionsRegex})`}
|
path={`/:section(${sectionsRegex})`}
|
||||||
|
|
|
@ -14,13 +14,13 @@ const style = {
|
||||||
|
|
||||||
export const RulesListSandbox = () => {
|
export const RulesListSandbox = () => {
|
||||||
const {
|
const {
|
||||||
services: { validateEmailAddresses },
|
services: { validateEmailAddresses, enabledEmailServices },
|
||||||
} = useConnectorContext();
|
} = useConnectorContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={style}>
|
<div style={style}>
|
||||||
{getRulesListLazy({
|
{getRulesListLazy({
|
||||||
connectorServices: { validateEmailAddresses },
|
connectorServices: { validateEmailAddresses, enabledEmailServices },
|
||||||
rulesListProps: {},
|
rulesListProps: {},
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -120,12 +120,12 @@ export const App = ({ deps }: { deps: TriggersAndActionsUiServices }) => {
|
||||||
|
|
||||||
export const AppWithoutRouter = ({ sectionsRegex }: { sectionsRegex: string }) => {
|
export const AppWithoutRouter = ({ sectionsRegex }: { sectionsRegex: string }) => {
|
||||||
const {
|
const {
|
||||||
actions: { validateEmailAddresses },
|
actions: { validateEmailAddresses, enabledEmailServices },
|
||||||
application: { navigateToApp },
|
application: { navigateToApp },
|
||||||
} = useKibana().services;
|
} = useKibana().services;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConnectorProvider value={{ services: { validateEmailAddresses } }}>
|
<ConnectorProvider value={{ services: { validateEmailAddresses, enabledEmailServices } }}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
|
|
|
@ -27,7 +27,7 @@ export const createStartServicesMock = (): TriggersAndActionsUiServices => {
|
||||||
const licensingPluginMock = licensingMock.createStart();
|
const licensingPluginMock = licensingMock.createStart();
|
||||||
return {
|
return {
|
||||||
...core,
|
...core,
|
||||||
actions: { validateEmailAddresses: jest.fn() },
|
actions: { validateEmailAddresses: jest.fn(), enabledEmailServices: ['*'] },
|
||||||
ruleTypeRegistry: {
|
ruleTypeRegistry: {
|
||||||
has: jest.fn(),
|
has: jest.fn(),
|
||||||
register: jest.fn(),
|
register: jest.fn(),
|
||||||
|
|
|
@ -49,7 +49,7 @@ import { getUntrackModalLazy } from './common/get_untrack_modal';
|
||||||
function createStartMock(): TriggersAndActionsUIPublicPluginStart {
|
function createStartMock(): TriggersAndActionsUIPublicPluginStart {
|
||||||
const actionTypeRegistry = new TypeRegistry<ActionTypeModel>();
|
const actionTypeRegistry = new TypeRegistry<ActionTypeModel>();
|
||||||
const ruleTypeRegistry = new TypeRegistry<RuleTypeModel>();
|
const ruleTypeRegistry = new TypeRegistry<RuleTypeModel>();
|
||||||
const connectorServices = { validateEmailAddresses: jest.fn() };
|
const connectorServices = { validateEmailAddresses: jest.fn(), enabledEmailServices: ['*'] };
|
||||||
return {
|
return {
|
||||||
actionTypeRegistry,
|
actionTypeRegistry,
|
||||||
ruleTypeRegistry,
|
ruleTypeRegistry,
|
||||||
|
|
|
@ -205,6 +205,7 @@ export class Plugin
|
||||||
const ruleTypeRegistry = this.ruleTypeRegistry;
|
const ruleTypeRegistry = this.ruleTypeRegistry;
|
||||||
this.connectorServices = {
|
this.connectorServices = {
|
||||||
validateEmailAddresses: plugins.actions.validateEmailAddresses,
|
validateEmailAddresses: plugins.actions.validateEmailAddresses,
|
||||||
|
enabledEmailServices: plugins.actions.enabledEmailServices,
|
||||||
};
|
};
|
||||||
|
|
||||||
ExperimentalFeaturesService.init({ experimentalFeatures: this.experimentalFeatures });
|
ExperimentalFeaturesService.init({ experimentalFeatures: this.experimentalFeatures });
|
||||||
|
|
|
@ -400,6 +400,7 @@ export interface SnoozeSchedule {
|
||||||
|
|
||||||
export interface ConnectorServices {
|
export interface ConnectorServices {
|
||||||
validateEmailAddresses: ActionsPublicPluginSetup['validateEmailAddresses'];
|
validateEmailAddresses: ActionsPublicPluginSetup['validateEmailAddresses'];
|
||||||
|
enabledEmailServices: ActionsPublicPluginSetup['enabledEmailServices'];
|
||||||
isWebhookSslWithPfxEnabled?: ActionsPublicPluginSetup['isWebhookSslWithPfxEnabled'];
|
isWebhookSslWithPfxEnabled?: ActionsPublicPluginSetup['isWebhookSslWithPfxEnabled'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const emailEnabledServices = ['google-mail', 'amazon-ses'];
|
||||||
|
|
||||||
|
import { FtrConfigProviderContext } from '@kbn/test';
|
||||||
|
|
||||||
|
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||||
|
const baseConfig = await readConfigFile(require.resolve('../../../../config.base.ts'));
|
||||||
|
|
||||||
|
return {
|
||||||
|
...baseConfig.getAll(),
|
||||||
|
testFiles: [require.resolve('.')],
|
||||||
|
junit: {
|
||||||
|
reportName:
|
||||||
|
'Chrome X-Pack UI Functional Tests with ES SSL - Email services enabled Kibana config',
|
||||||
|
},
|
||||||
|
kbnTestServer: {
|
||||||
|
...baseConfig.getAll().kbnTestServer,
|
||||||
|
serverArgs: [
|
||||||
|
...baseConfig.getAll().kbnTestServer.serverArgs,
|
||||||
|
`--xpack.actions.email.services.enabled=${JSON.stringify(emailEnabledServices)}`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import expect from '@kbn/expect';
|
||||||
|
import { FtrProviderContext } from '../../../../ftr_provider_context';
|
||||||
|
|
||||||
|
export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
||||||
|
const testSubjects = getService('testSubjects');
|
||||||
|
const pageObjects = getPageObjects(['common', 'triggersActionsUI', 'header']);
|
||||||
|
const find = getService('find');
|
||||||
|
|
||||||
|
describe('Email - with multiple enabled services config', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await pageObjects.common.navigateToApp('triggersActionsConnectors');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use the kibana config for enabled services', async () => {
|
||||||
|
await pageObjects.triggersActionsUI.clickCreateConnectorButton();
|
||||||
|
await testSubjects.click('.email-card');
|
||||||
|
const emailServicesOptions = await find.allByCssSelector(
|
||||||
|
'[data-test-subj="emailServiceSelectInput"] > option'
|
||||||
|
);
|
||||||
|
expect(emailServicesOptions.length).to.be(3);
|
||||||
|
expect(await emailServicesOptions[0].getVisibleText()).to.be(' '); // empty option
|
||||||
|
expect(await emailServicesOptions[1].getVisibleText()).to.be('Gmail');
|
||||||
|
expect(await emailServicesOptions[2].getVisibleText()).to.be('Amazon SES');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* 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 { FtrProviderContext } from '@kbn/test-suites-xpack-platform/alerting_api_integration/common/ftr_provider_context';
|
||||||
|
import {
|
||||||
|
buildUp,
|
||||||
|
tearDown,
|
||||||
|
} from '@kbn/test-suites-xpack-platform/alerting_api_integration/spaces_only/tests/helpers';
|
||||||
|
|
||||||
|
export default function actionsTests({ loadTestFile, getService }: FtrProviderContext) {
|
||||||
|
describe('Connectors with email services enabled Kibana config', () => {
|
||||||
|
before(async () => buildUp(getService));
|
||||||
|
after(async () => tearDown(getService));
|
||||||
|
|
||||||
|
loadTestFile(require.resolve('./email'));
|
||||||
|
});
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue