diff --git a/docs/settings/alert-action-settings.asciidoc b/docs/settings/alert-action-settings.asciidoc index 0c66deac68c0d..b7d7e8d344a32 100644 --- a/docs/settings/alert-action-settings.asciidoc +++ b/docs/settings/alert-action-settings.asciidoc @@ -142,6 +142,15 @@ A list of action types that are enabled. It defaults to `["*"]`, enabling all ty + Disabled action types will not appear as an option when creating new connectors, but existing connectors and actions of that type will remain in {kib} and will not function. +`xpack.actions.microsoftExchangeUrl`:: +The URL for the Microsoft Azure Active Directory endpoint to use for MS Exchange email authentication. Default: `https://login.microsoftonline.com`. + +`xpack.actions.microsoftGraphApiUrl`:: +The URL for the Microsoft Graph API endpoint to use for MS Exchange email authentication. Default: `https://graph.microsoft.com/v1.0`. + +`xpack.actions.microsoftGraphApiScope`:: +The URL for the Microsoft Graph API scope endpoint to use for MS Exchange email authentication. Default: `https://graph.microsoft.com/.default`. + `xpack.actions.proxyUrl` {ess-icon}:: Specifies the proxy URL to use, if using a proxy for actions. By default, no proxy is used. + diff --git a/x-pack/plugins/actions/common/index.ts b/x-pack/plugins/actions/common/index.ts index feeb07fe84477..b56f6c61238c2 100644 --- a/x-pack/plugins/actions/common/index.ts +++ b/x-pack/plugins/actions/common/index.ts @@ -19,3 +19,7 @@ export * from './execution_log_types'; export const BASE_ACTION_API_PATH = '/api/actions'; export const INTERNAL_BASE_ACTION_API_PATH = '/internal/actions'; export const ACTIONS_FEATURE_ID = 'actions'; + +export const DEFAULT_MICROSOFT_EXCHANGE_URL = 'https://login.microsoftonline.com'; +export const DEFAULT_MICROSOFT_GRAPH_API_URL = 'https://graph.microsoft.com/v1.0'; +export const DEFAULT_MICROSOFT_GRAPH_API_SCOPE = 'https://graph.microsoft.com/.default'; diff --git a/x-pack/plugins/actions/server/actions_client/actions_client.test.ts b/x-pack/plugins/actions/server/actions_client/actions_client.test.ts index b52ce10ab8c4a..ff5258ef43cca 100644 --- a/x-pack/plugins/actions/server/actions_client/actions_client.test.ts +++ b/x-pack/plugins/actions/server/actions_client/actions_client.test.ts @@ -8,7 +8,11 @@ import { schema } from '@kbn/config-schema'; import moment from 'moment'; import { ByteSizeValue } from '@kbn/config-schema'; - +import { + DEFAULT_MICROSOFT_EXCHANGE_URL, + DEFAULT_MICROSOFT_GRAPH_API_SCOPE, + DEFAULT_MICROSOFT_GRAPH_API_URL, +} from '../../common'; import { ActionTypeRegistry, ActionTypeRegistryOpts } from '../action_type_registry'; import { ActionsClient } from './actions_client'; import { ExecutorType, ActionType } from '../types'; @@ -596,6 +600,9 @@ describe('create()', () => { proxyVerificationMode: 'full', }, enableFooterInEmail: true, + microsoftGraphApiUrl: DEFAULT_MICROSOFT_GRAPH_API_URL, + microsoftGraphApiScope: DEFAULT_MICROSOFT_GRAPH_API_SCOPE, + microsoftExchangeUrl: DEFAULT_MICROSOFT_EXCHANGE_URL, }); const localActionTypeRegistryParams = { diff --git a/x-pack/plugins/actions/server/actions_config.mock.ts b/x-pack/plugins/actions/server/actions_config.mock.ts index 49f1a807bce9f..0b95b0d831655 100644 --- a/x-pack/plugins/actions/server/actions_config.mock.ts +++ b/x-pack/plugins/actions/server/actions_config.mock.ts @@ -5,6 +5,11 @@ * 2.0. */ +import { + DEFAULT_MICROSOFT_EXCHANGE_URL, + DEFAULT_MICROSOFT_GRAPH_API_SCOPE, + DEFAULT_MICROSOFT_GRAPH_API_URL, +} from '../common'; import { ActionsConfigurationUtilities } from './actions_config'; const createActionsConfigMock = () => { @@ -24,7 +29,9 @@ const createActionsConfigMock = () => { timeout: 360000, }), getCustomHostSettings: jest.fn().mockReturnValue(undefined), - getMicrosoftGraphApiUrl: jest.fn().mockReturnValue(undefined), + getMicrosoftGraphApiUrl: jest.fn().mockReturnValue(DEFAULT_MICROSOFT_GRAPH_API_URL), + getMicrosoftGraphApiScope: jest.fn().mockReturnValue(DEFAULT_MICROSOFT_GRAPH_API_SCOPE), + getMicrosoftExchangeUrl: jest.fn().mockReturnValue(DEFAULT_MICROSOFT_EXCHANGE_URL), validateEmailAddresses: jest.fn().mockReturnValue(undefined), getMaxAttempts: jest.fn().mockReturnValue(3), enableFooterInEmail: jest.fn().mockReturnValue(true), diff --git a/x-pack/plugins/actions/server/actions_config.test.ts b/x-pack/plugins/actions/server/actions_config.test.ts index d19fcbc363b88..a6966e0e85c40 100644 --- a/x-pack/plugins/actions/server/actions_config.test.ts +++ b/x-pack/plugins/actions/server/actions_config.test.ts @@ -7,6 +7,11 @@ import { ByteSizeValue } from '@kbn/config-schema'; import { ActionsConfig } from './config'; +import { + DEFAULT_MICROSOFT_EXCHANGE_URL, + DEFAULT_MICROSOFT_GRAPH_API_SCOPE, + DEFAULT_MICROSOFT_GRAPH_API_URL, +} from '../common'; import { getActionsConfigurationUtilities, AllowedHosts, @@ -34,6 +39,9 @@ const defaultActionsConfig: ActionsConfig = { verificationMode: 'full', }, enableFooterInEmail: true, + microsoftGraphApiUrl: DEFAULT_MICROSOFT_GRAPH_API_URL, + microsoftGraphApiScope: DEFAULT_MICROSOFT_GRAPH_API_SCOPE, + microsoftExchangeUrl: DEFAULT_MICROSOFT_EXCHANGE_URL, }; describe('ensureUriAllowed', () => { diff --git a/x-pack/plugins/actions/server/actions_config.ts b/x-pack/plugins/actions/server/actions_config.ts index 240a65228b4dc..e77c0528d16a1 100644 --- a/x-pack/plugins/actions/server/actions_config.ts +++ b/x-pack/plugins/actions/server/actions_config.ts @@ -47,7 +47,9 @@ export interface ActionsConfigurationUtilities { getProxySettings: () => undefined | ProxySettings; getResponseSettings: () => ResponseSettings; getCustomHostSettings: (targetUrl: string) => CustomHostSettings | undefined; - getMicrosoftGraphApiUrl: () => undefined | string; + getMicrosoftGraphApiUrl: () => string; + getMicrosoftGraphApiScope: () => string; + getMicrosoftExchangeUrl: () => string; getMaxAttempts: ({ actionTypeMaxAttempts, actionTypeId, @@ -127,10 +129,18 @@ function getProxySettingsFromConfig(config: ActionsConfig): undefined | ProxySet }; } -function getMicrosoftGraphApiUrlFromConfig(config: ActionsConfig): undefined | string { +function getMicrosoftGraphApiUrlFromConfig(config: ActionsConfig): string { return config.microsoftGraphApiUrl; } +function getMicrosoftGraphApiScopeFromConfig(config: ActionsConfig): string { + return config.microsoftGraphApiScope; +} + +function getMicrosoftExchangeUrlFromConfig(config: ActionsConfig): string { + return config.microsoftExchangeUrl; +} + function arrayAsSet(arr: T[] | undefined): Set | undefined { if (!arr) return; return new Set(arr); @@ -209,6 +219,8 @@ export function getActionsConfigurationUtilities( }, getCustomHostSettings: (targetUrl: string) => getCustomHostSettings(config, targetUrl), getMicrosoftGraphApiUrl: () => getMicrosoftGraphApiUrlFromConfig(config), + getMicrosoftGraphApiScope: () => getMicrosoftGraphApiScopeFromConfig(config), + getMicrosoftExchangeUrl: () => getMicrosoftExchangeUrlFromConfig(config), validateEmailAddresses: (addresses: string[], options: ValidateEmailAddressesOptions) => validatedEmailCurried(addresses, options), getMaxAttempts: ({ actionTypeMaxAttempts, actionTypeId }) => { diff --git a/x-pack/plugins/actions/server/config.test.ts b/x-pack/plugins/actions/server/config.test.ts index f5971a8650681..5adc9c18b07a7 100644 --- a/x-pack/plugins/actions/server/config.test.ts +++ b/x-pack/plugins/actions/server/config.test.ts @@ -30,6 +30,9 @@ describe('config validation', () => { "maxResponseContentLength": ByteSizeValue { "valueInBytes": 1048576, }, + "microsoftExchangeUrl": "https://login.microsoftonline.com", + "microsoftGraphApiScope": "https://graph.microsoft.com/.default", + "microsoftGraphApiUrl": "https://graph.microsoft.com/v1.0", "preconfigured": Object {}, "preconfiguredAlertHistoryEsIndex": false, "proxyRejectUnauthorizedCertificates": true, @@ -65,6 +68,9 @@ describe('config validation', () => { "maxResponseContentLength": ByteSizeValue { "valueInBytes": 1048576, }, + "microsoftExchangeUrl": "https://login.microsoftonline.com", + "microsoftGraphApiScope": "https://graph.microsoft.com/.default", + "microsoftGraphApiUrl": "https://graph.microsoft.com/v1.0", "preconfigured": Object { "mySlack1": Object { "actionTypeId": ".slack", @@ -147,7 +153,7 @@ describe('config validation', () => { expect(mockLogger.warn.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "The confgurations xpack.actions.proxyBypassHosts and xpack.actions.proxyOnlyHosts can not be used at the same time. The configuration xpack.actions.proxyOnlyHosts will be ignored.", + "The configurations xpack.actions.proxyBypassHosts and xpack.actions.proxyOnlyHosts can not be used at the same time. The configuration xpack.actions.proxyOnlyHosts will be ignored.", ], ] `); @@ -169,7 +175,7 @@ describe('config validation', () => { expect(mockLogger.warn.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "The confguration xpack.actions.proxyUrl: bad url is invalid.", + "The configuration xpack.actions.proxyUrl: bad url is invalid.", ], ] `); @@ -207,6 +213,9 @@ describe('config validation', () => { "maxResponseContentLength": ByteSizeValue { "valueInBytes": 1048576, }, + "microsoftExchangeUrl": "https://login.microsoftonline.com", + "microsoftGraphApiScope": "https://graph.microsoft.com/.default", + "microsoftGraphApiUrl": "https://graph.microsoft.com/v1.0", "preconfigured": Object {}, "preconfiguredAlertHistoryEsIndex": false, "proxyRejectUnauthorizedCertificates": true, diff --git a/x-pack/plugins/actions/server/config.ts b/x-pack/plugins/actions/server/config.ts index a0b6c23883993..980144117e4e8 100644 --- a/x-pack/plugins/actions/server/config.ts +++ b/x-pack/plugins/actions/server/config.ts @@ -7,6 +7,11 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { Logger } from '@kbn/core/server'; +import { + DEFAULT_MICROSOFT_EXCHANGE_URL, + DEFAULT_MICROSOFT_GRAPH_API_SCOPE, + DEFAULT_MICROSOFT_GRAPH_API_URL, +} from '../common'; export enum AllowedHosts { Any = '*', @@ -120,7 +125,9 @@ export const configSchema = schema.object({ maxResponseContentLength: schema.byteSize({ defaultValue: '1mb' }), responseTimeout: schema.duration({ defaultValue: '60s' }), customHostSettings: schema.maybe(schema.arrayOf(customHostSettingsSchema)), - microsoftGraphApiUrl: schema.maybe(schema.string()), + microsoftGraphApiUrl: schema.string({ defaultValue: DEFAULT_MICROSOFT_GRAPH_API_URL }), + microsoftGraphApiScope: schema.string({ defaultValue: DEFAULT_MICROSOFT_GRAPH_API_SCOPE }), + microsoftExchangeUrl: schema.string({ defaultValue: DEFAULT_MICROSOFT_EXCHANGE_URL }), email: schema.maybe( schema.object({ domain_allowlist: schema.arrayOf(schema.string()), @@ -155,13 +162,13 @@ export function getValidatedConfig(logger: Logger, originalConfig: ActionsConfig try { new URL(proxyUrl); } catch (err) { - logger.warn(`The confguration xpack.actions.proxyUrl: ${proxyUrl} is invalid.`); + logger.warn(`The configuration xpack.actions.proxyUrl: ${proxyUrl} is invalid.`); } } if (proxyBypassHosts && proxyOnlyHosts) { logger.warn( - 'The confgurations xpack.actions.proxyBypassHosts and xpack.actions.proxyOnlyHosts can not be used at the same time. The configuration xpack.actions.proxyOnlyHosts will be ignored.' + 'The configurations xpack.actions.proxyBypassHosts and xpack.actions.proxyOnlyHosts can not be used at the same time. The configuration xpack.actions.proxyOnlyHosts will be ignored.' ); const tmp: Record = originalConfig; delete tmp.proxyOnlyHosts; diff --git a/x-pack/plugins/actions/server/integration_tests/axios_utils_connection.test.ts b/x-pack/plugins/actions/server/integration_tests/axios_utils_connection.test.ts index 21d2f09537727..200656d339ac3 100644 --- a/x-pack/plugins/actions/server/integration_tests/axios_utils_connection.test.ts +++ b/x-pack/plugins/actions/server/integration_tests/axios_utils_connection.test.ts @@ -23,6 +23,11 @@ import { createReadySignal } from '@kbn/event-log-plugin/server/lib/ready_signal import { ActionsConfig } from '../config'; import { ActionsConfigurationUtilities, getActionsConfigurationUtilities } from '../actions_config'; import { resolveCustomHosts } from '../lib/custom_host_settings'; +import { + DEFAULT_MICROSOFT_EXCHANGE_URL, + DEFAULT_MICROSOFT_GRAPH_API_SCOPE, + DEFAULT_MICROSOFT_GRAPH_API_URL, +} from '../../common'; const logger = loggingSystemMock.create().get() as jest.Mocked; @@ -683,6 +688,9 @@ const BaseActionsConfig: ActionsConfig = { responseTimeout: momentDuration(1000 * 30), customHostSettings: undefined, enableFooterInEmail: true, + microsoftGraphApiUrl: DEFAULT_MICROSOFT_GRAPH_API_URL, + microsoftGraphApiScope: DEFAULT_MICROSOFT_GRAPH_API_SCOPE, + microsoftExchangeUrl: DEFAULT_MICROSOFT_EXCHANGE_URL, }; function getACUfromConfig(config: Partial = {}): ActionsConfigurationUtilities { diff --git a/x-pack/plugins/actions/server/integration_tests/axios_utils_proxy.test.ts b/x-pack/plugins/actions/server/integration_tests/axios_utils_proxy.test.ts index d5dd8ae91632e..f29b2a9855186 100644 --- a/x-pack/plugins/actions/server/integration_tests/axios_utils_proxy.test.ts +++ b/x-pack/plugins/actions/server/integration_tests/axios_utils_proxy.test.ts @@ -23,6 +23,11 @@ import { createReadySignal } from '@kbn/event-log-plugin/server/lib/ready_signal import { ActionsConfig } from '../config'; import { ActionsConfigurationUtilities, getActionsConfigurationUtilities } from '../actions_config'; import { resolveCustomHosts } from '../lib/custom_host_settings'; +import { + DEFAULT_MICROSOFT_GRAPH_API_URL, + DEFAULT_MICROSOFT_GRAPH_API_SCOPE, + DEFAULT_MICROSOFT_EXCHANGE_URL, +} from '../../common'; const logger = loggingSystemMock.create().get() as jest.Mocked; @@ -589,6 +594,9 @@ const BaseActionsConfig: ActionsConfig = { responseTimeout: momentDuration(1000 * 30), customHostSettings: undefined, enableFooterInEmail: true, + microsoftGraphApiUrl: DEFAULT_MICROSOFT_GRAPH_API_URL, + microsoftGraphApiScope: DEFAULT_MICROSOFT_GRAPH_API_SCOPE, + microsoftExchangeUrl: DEFAULT_MICROSOFT_EXCHANGE_URL, }; function getACUfromConfig(config: Partial = {}): ActionsConfigurationUtilities { diff --git a/x-pack/plugins/actions/server/lib/custom_host_settings.test.ts b/x-pack/plugins/actions/server/lib/custom_host_settings.test.ts index f5cd9c268bd7e..818d7fb9bcd0a 100644 --- a/x-pack/plugins/actions/server/lib/custom_host_settings.test.ts +++ b/x-pack/plugins/actions/server/lib/custom_host_settings.test.ts @@ -15,6 +15,11 @@ import { Logger } from '@kbn/core/server'; import { loggingSystemMock } from '@kbn/core/server/mocks'; import { resolveCustomHosts, getCanonicalCustomHostUrl } from './custom_host_settings'; +import { + DEFAULT_MICROSOFT_GRAPH_API_URL, + DEFAULT_MICROSOFT_GRAPH_API_SCOPE, + DEFAULT_MICROSOFT_EXCHANGE_URL, +} from '../../common'; const CA_DIR = '../../../../../../packages/kbn-dev-utils/certs'; const CA_FILE1 = pathResolve(__filename, pathJoin(CA_DIR, 'ca.crt')); @@ -74,6 +79,9 @@ describe('custom_host_settings', () => { maxResponseContentLength: new ByteSizeValue(1000000), responseTimeout: moment.duration(60000), enableFooterInEmail: true, + microsoftGraphApiUrl: DEFAULT_MICROSOFT_GRAPH_API_URL, + microsoftGraphApiScope: DEFAULT_MICROSOFT_GRAPH_API_SCOPE, + microsoftExchangeUrl: DEFAULT_MICROSOFT_EXCHANGE_URL, }; test('ensure it copies over the config parts that it does not touch', () => { diff --git a/x-pack/plugins/actions/server/plugin.test.ts b/x-pack/plugins/actions/server/plugin.test.ts index dd936600d7055..004da4cac0339 100644 --- a/x-pack/plugins/actions/server/plugin.test.ts +++ b/x-pack/plugins/actions/server/plugin.test.ts @@ -24,7 +24,12 @@ import { ActionsPluginsStart, PluginSetupContract, } from './plugin'; -import { AlertHistoryEsIndexConnectorId } from '../common'; +import { + AlertHistoryEsIndexConnectorId, + DEFAULT_MICROSOFT_EXCHANGE_URL, + DEFAULT_MICROSOFT_GRAPH_API_SCOPE, + DEFAULT_MICROSOFT_GRAPH_API_URL, +} from '../common'; const executor: ExecutorType<{}, {}, {}, void> = async (options) => { return { status: 'ok', actionId: options.actionId }; @@ -51,6 +56,9 @@ function getConfig(overrides = {}) { maxResponseContentLength: new ByteSizeValue(1000000), responseTimeout: moment.duration('60s'), enableFooterInEmail: true, + microsoftGraphApiUrl: DEFAULT_MICROSOFT_GRAPH_API_URL, + microsoftGraphApiScope: DEFAULT_MICROSOFT_GRAPH_API_SCOPE, + microsoftExchangeUrl: DEFAULT_MICROSOFT_EXCHANGE_URL, ...overrides, }; } @@ -73,6 +81,9 @@ describe('Actions Plugin', () => { maxResponseContentLength: new ByteSizeValue(1000000), responseTimeout: moment.duration(60000), enableFooterInEmail: true, + microsoftGraphApiUrl: DEFAULT_MICROSOFT_GRAPH_API_URL, + microsoftGraphApiScope: DEFAULT_MICROSOFT_GRAPH_API_SCOPE, + microsoftExchangeUrl: DEFAULT_MICROSOFT_EXCHANGE_URL, }); plugin = new ActionsPlugin(context); coreSetup = coreMock.createSetup(); @@ -534,6 +545,9 @@ describe('Actions Plugin', () => { maxResponseContentLength: new ByteSizeValue(1000000), responseTimeout: moment.duration(60000), enableFooterInEmail: true, + microsoftGraphApiUrl: DEFAULT_MICROSOFT_GRAPH_API_URL, + microsoftGraphApiScope: DEFAULT_MICROSOFT_GRAPH_API_SCOPE, + microsoftExchangeUrl: DEFAULT_MICROSOFT_EXCHANGE_URL, }); plugin = new ActionsPlugin(context); coreSetup = coreMock.createSetup(); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/email/send_email.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/email/send_email.test.ts index 5a27b80448017..535c3932c04e7 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/email/send_email.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/email/send_email.test.ts @@ -179,7 +179,6 @@ describe('send_email module', () => { expect(sendEmailGraphApiMock.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { - "graphApiUrl": undefined, "headers": Object { "Authorization": "Bearer dfjsdfgdjhfgsjdf", "Content-Type": "application/json", @@ -232,6 +231,82 @@ describe('send_email module', () => { `); }); + test('uses custom graph API scope if configured for OAuth 2.0 Client Credentials authentication for email using "exchange_server" service', async () => { + const sendEmailGraphApiMock = sendEmailGraphApi as jest.Mock; + const getOAuthClientCredentialsAccessTokenMock = + getOAuthClientCredentialsAccessToken as jest.Mock; + const sendEmailOptions = getSendEmailOptions({ + transport: { + service: 'exchange_server', + clientId: '123456', + tenantId: '98765', + clientSecret: 'sdfhkdsjhfksdjfh', + }, + }); + sendEmailOptions.configurationUtilities.getMicrosoftGraphApiScope.mockReturnValueOnce( + 'https://dod-graph.microsoft.us/.default' + ); + getOAuthClientCredentialsAccessTokenMock.mockReturnValueOnce(`Bearer dfjsdfgdjhfgsjdf`); + const date = new Date(); + date.setDate(date.getDate() + 5); + + sendEmailGraphApiMock.mockReturnValue({ + status: 202, + }); + + await sendEmail(mockLogger, sendEmailOptions, connectorTokenClient); + expect(getOAuthClientCredentialsAccessTokenMock).toHaveBeenCalledWith({ + configurationUtilities: sendEmailOptions.configurationUtilities, + connectorId: '1', + connectorTokenClient, + credentials: { + config: { clientId: '123456', tenantId: '98765' }, + secrets: { clientSecret: 'sdfhkdsjhfksdjfh' }, + }, + logger: mockLogger, + oAuthScope: 'https://dod-graph.microsoft.us/.default', + tokenUrl: 'https://login.microsoftonline.com/98765/oauth2/v2.0/token', + }); + }); + + test('uses custom exchange URL if configured for OAuth 2.0 Client Credentials authentication for email using "exchange_server" service', async () => { + const sendEmailGraphApiMock = sendEmailGraphApi as jest.Mock; + const getOAuthClientCredentialsAccessTokenMock = + getOAuthClientCredentialsAccessToken as jest.Mock; + const sendEmailOptions = getSendEmailOptions({ + transport: { + service: 'exchange_server', + clientId: '123456', + tenantId: '98765', + clientSecret: 'sdfhkdsjhfksdjfh', + }, + }); + sendEmailOptions.configurationUtilities.getMicrosoftExchangeUrl.mockReturnValueOnce( + 'https://login.microsoftonline.us' + ); + getOAuthClientCredentialsAccessTokenMock.mockReturnValueOnce(`Bearer dfjsdfgdjhfgsjdf`); + const date = new Date(); + date.setDate(date.getDate() + 5); + + sendEmailGraphApiMock.mockReturnValue({ + status: 202, + }); + + await sendEmail(mockLogger, sendEmailOptions, connectorTokenClient); + expect(getOAuthClientCredentialsAccessTokenMock).toHaveBeenCalledWith({ + configurationUtilities: sendEmailOptions.configurationUtilities, + connectorId: '1', + connectorTokenClient, + credentials: { + config: { clientId: '123456', tenantId: '98765' }, + secrets: { clientSecret: 'sdfhkdsjhfksdjfh' }, + }, + logger: mockLogger, + oAuthScope: 'https://graph.microsoft.com/.default', + tokenUrl: 'https://login.microsoftonline.us/98765/oauth2/v2.0/token', + }); + }); + test('throws error if null access token returned when using OAuth 2.0 Client Credentials authentication', async () => { const sendEmailGraphApiMock = sendEmailGraphApi as jest.Mock; const getOAuthClientCredentialsAccessTokenMock = diff --git a/x-pack/plugins/stack_connectors/server/connector_types/email/send_email.ts b/x-pack/plugins/stack_connectors/server/connector_types/email/send_email.ts index 6c24848d6571e..f3ab3bfa22c55 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/email/send_email.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/email/send_email.ts @@ -25,8 +25,6 @@ import { sendEmailGraphApi } from './send_email_graph_api'; // an email "service" which doesn't actually send, just returns what it would send export const JSON_TRANSPORT_SERVICE = '__json'; // The value is the resource identifier (Application ID URI) of the resource you want, affixed with the .default suffix. For Microsoft Graph, the value is https://graph.microsoft.com/.default. This value informs the Microsoft identity platform endpoint that of all the application permissions you have configured for your app in the app registration portal, it should issue a token for the ones associated with the resource you want to use. -export const GRAPH_API_OAUTH_SCOPE = 'https://graph.microsoft.com/.default'; -export const EXCHANGE_ONLINE_SERVER_HOST = 'https://login.microsoftonline.com'; export interface SendEmailOptions { connectorId: string; @@ -92,6 +90,12 @@ export async function sendEmailWithExchange( const { transport, configurationUtilities, connectorId } = options; const { clientId, clientSecret, tenantId, oauthTokenUrl } = transport; + let tokenUrl = oauthTokenUrl; + if (!tokenUrl) { + const exchangeUrl = configurationUtilities.getMicrosoftExchangeUrl(); + tokenUrl = `${exchangeUrl}/${tenantId}/oauth2/v2.0/token`; + } + const accessToken = await getOAuthClientCredentialsAccessToken({ connectorId, logger, @@ -105,8 +109,8 @@ export async function sendEmailWithExchange( clientSecret: clientSecret as string, }, }, - oAuthScope: GRAPH_API_OAUTH_SCOPE, - tokenUrl: oauthTokenUrl ?? `${EXCHANGE_ONLINE_SERVER_HOST}/${tenantId}/oauth2/v2.0/token`, + oAuthScope: configurationUtilities.getMicrosoftGraphApiScope(), + tokenUrl, connectorTokenClient, }); @@ -148,7 +152,6 @@ export async function sendEmailWithExchange( options, headers, messageHTML, - graphApiUrl: configurationUtilities.getMicrosoftGraphApiUrl(), }, logger, configurationUtilities, diff --git a/x-pack/plugins/stack_connectors/server/connector_types/email/send_email_graph_api.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/email/send_email_graph_api.test.ts index bbb5fa2ae5f4e..40c282103f17c 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/email/send_email_graph_api.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/email/send_email_graph_api.test.ts @@ -32,7 +32,11 @@ describe('sendEmailGraphApi', () => { status: 202, }); await sendEmailGraphApi( - { options: getSendEmailOptions(), messageHTML: 'test1', headers: {} }, + { + options: getSendEmailOptions(), + messageHTML: 'test1', + headers: {}, + }, logger, configurationUtilities ); @@ -207,12 +211,12 @@ describe('sendEmailGraphApi', () => { axiosInstanceMock.mockReturnValueOnce({ status: 202, }); + configurationUtilities.getMicrosoftGraphApiUrl.mockReturnValueOnce('https://test'); await sendEmailGraphApi( { options: getSendEmailOptions(), messageHTML: 'test3', headers: {}, - graphApiUrl: 'https://test', }, logger, configurationUtilities diff --git a/x-pack/plugins/stack_connectors/server/connector_types/email/send_email_graph_api.ts b/x-pack/plugins/stack_connectors/server/connector_types/email/send_email_graph_api.ts index 40177e50a0d18..79d7af05e041e 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/email/send_email_graph_api.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/email/send_email_graph_api.ts @@ -17,18 +17,15 @@ interface SendEmailGraphApiOptions { options: SendEmailOptions; headers: Record; messageHTML: string; - graphApiUrl?: string; } -const MICROSOFT_GRAPH_API_HOST = 'https://graph.microsoft.com/v1.0'; - export async function sendEmailGraphApi( sendEmailOptions: SendEmailGraphApiOptions, logger: Logger, configurationUtilities: ActionsConfigurationUtilities, axiosInstance?: AxiosInstance ): Promise { - const { options, headers, messageHTML, graphApiUrl } = sendEmailOptions; + const { options, headers, messageHTML } = sendEmailOptions; // Create a new axios instance if one is not provided axiosInstance = axiosInstance ?? axios.create(); @@ -36,7 +33,9 @@ export async function sendEmailGraphApi( // POST /users/{id | userPrincipalName}/sendMail const res = await request({ axios: axiosInstance, - url: `${graphApiUrl ?? MICROSOFT_GRAPH_API_HOST}/users/${options.routing.from}/sendMail`, + url: `${configurationUtilities.getMicrosoftGraphApiUrl()}/users/${ + options.routing.from + }/sendMail`, method: 'post', logger, data: getMessage(options, messageHTML),