From 0313a19dbb32b79c053061823206882d870b5c9e Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Thu, 1 Feb 2024 12:41:52 -0500 Subject: [PATCH] [Response Ops][Actions] Adding configuration to override default MS Graph API Scope and Exchange URL values (#175812) Resolves https://github.com/elastic/kibana/issues/166064 ## Summary Adds the following configurations to the `kibana.yml` config: * `xpack.actions.microsoftGraphApiScope` - overrides the default Graph API scope value of `https://graph.microsoft.com/.default` * `xpack.actions.microsoftExchangeUrl` - overrides the default value of `https://login.microsoftonline.com` This allows users in different Azure environments to customize their endpoints as needed. ## To Verify We are unable to test this in a different environment but we can verify that the config overrides the defaults as expected by setting the config values to something different and the logging out the params that are sent to `getOAuthClientCredentialsAccessToken` in `x-pack/plugins/stack_connectors/server/connector_types/email/send_email.ts`. Then create an MS Exchange email connector and test it to see that the logged values are overridden as expected. --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> (cherry picked from commit f7e4f7a636763d46cb6a38b21a5eb6e67595ddfe) --- docs/settings/alert-action-settings.asciidoc | 9 +++ x-pack/plugins/actions/common/index.ts | 4 + .../actions_client/actions_client.test.ts | 9 ++- .../actions/server/actions_config.mock.ts | 9 ++- .../actions/server/actions_config.test.ts | 8 ++ .../plugins/actions/server/actions_config.ts | 16 +++- x-pack/plugins/actions/server/config.test.ts | 13 +++- x-pack/plugins/actions/server/config.ts | 13 +++- .../axios_utils_connection.test.ts | 8 ++ .../axios_utils_proxy.test.ts | 8 ++ .../server/lib/custom_host_settings.test.ts | 8 ++ x-pack/plugins/actions/server/plugin.test.ts | 16 +++- .../connector_types/email/send_email.test.ts | 77 ++++++++++++++++++- .../connector_types/email/send_email.ts | 13 ++-- .../email/send_email_graph_api.test.ts | 8 +- .../email/send_email_graph_api.ts | 9 +-- 16 files changed, 205 insertions(+), 23 deletions(-) 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),