Skip to content

Commit

Permalink
Microsoft Outlook service root endpoint
Browse files Browse the repository at this point in the history
This change makes the service root configurable for the Microsoft Outlook nodes.

The service root endpoint is configurable in the Outlook credential.

The motivation behind this change is to allow the Microsoft Outlook N8N node to connect to the other tenant types such as the Microsoft GovCloud instances.
  • Loading branch information
kyle-blacklist committed Feb 24, 2025
1 parent 073b05b commit 1bb8e1b
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,24 @@ const scopes = [
'MailboxSettings.Read',
];

const enum EndpointNames {
Global = 'Microsoft Graph global service',
GovCloud = 'Microsoft Graph for US Government L4',
DoDCloud = 'Microsoft Graph for US Government L5 (DOD)',
China = 'Microsoft Graph China operated by 21Vianet',
}

/**
* The service endpoints are defined by Microsoft:
* https://learn.microsoft.com/en-us/graph/deployments#microsoft-graph-and-graph-explorer-service-root-endpoints
*/
const endpoints: Record<EndpointNames, string> = {
[EndpointNames.Global]: 'https://graph.microsoft.com',
[EndpointNames.GovCloud]: 'https://graph.microsoft.us',
[EndpointNames.DoDCloud]: 'https://dod-graph.microsoft.us',
[EndpointNames.China]: 'https://microsoftgraph.chinacloudapi.cn',
};

export class MicrosoftOutlookOAuth2Api implements ICredentialType {
name = 'microsoftOutlookOAuth2Api';

Expand Down Expand Up @@ -50,5 +68,16 @@ export class MicrosoftOutlookOAuth2Api implements ICredentialType {
},
},
},
{
displayName: 'Endpoint',
description: 'The service root endpoint to use when connecting to the Outlook API.',
name: 'graphEndpoint',
type: 'options',
default: endpoints[EndpointNames.Global],
options: Object.keys(endpoints).map((name) => ({
name,
value: endpoints[name as EndpointNames],
})),
},
];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import type { ICredentialDataDecryptedObject, IExecuteFunctions } from 'n8n-workflow';

import * as transport from '../../../v2/transport';

const fakeExecute = (credentials: ICredentialDataDecryptedObject, result: unknown) => {
const fakeExecuteFunction = {
async getCredentials(): Promise<ICredentialDataDecryptedObject> {
return credentials;
},

helpers: {
requestWithAuthentication: jest.fn().mockResolvedValue(result),
},
} as unknown as IExecuteFunctions;
return fakeExecuteFunction;
};

describe('Test MicrosoftOutlookV2, transport => microsoftApiRequest', () => {
it('should call microsoftApiRequest using the defined service root', async () => {
const execute = fakeExecute(
{
graphEndpoint: 'https://foo.bar',
useShared: false,
userPrincipalName: 'test-principal',
},
'foo',
);

const result: string = (await transport.microsoftApiRequest.call(
execute,
'GET',
'/foo',
)) as string;

expect(result).toEqual('foo');
expect(execute.helpers.requestWithAuthentication).toHaveBeenCalledTimes(1);
expect(execute.helpers.requestWithAuthentication).toHaveBeenCalledWith(
'microsoftOutlookOAuth2Api',
{
headers: { 'Content-Type': 'application/json' },
method: 'GET',
qs: {},
uri: 'https://foo.bar/v1.0/me/foo',
json: true,
},
);
});

it('should call microsoftApiRequest using the service root if no root is provided', async () => {
const execute = fakeExecute(
{
useShared: false,
userPrincipalName: 'test-principal',
},
'foo',
);

const result: string = (await transport.microsoftApiRequest.call(
execute,
'GET',
'/foo',
)) as string;

expect(result).toEqual('foo');
expect(execute.helpers.requestWithAuthentication).toHaveBeenCalledTimes(1);
expect(execute.helpers.requestWithAuthentication).toHaveBeenCalledWith(
'microsoftOutlookOAuth2Api',
{
headers: { 'Content-Type': 'application/json' },
method: 'GET',
qs: {},
uri: 'https://graph.microsoft.com/v1.0/me/foo',
json: true,
},
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@ export async function microsoftApiRequest(
option: IDataObject = { json: true },
) {
const credentials = await this.getCredentials('microsoftOutlookOAuth2Api');
const graphEndpoint = (credentials.graphEndpoint as string) || 'https://graph.microsoft.com';

let apiUrl = `https://graph.microsoft.com/v1.0/me${resource}`;
let apiUrl = `${graphEndpoint}/v1.0/me${resource}`;
// If accessing shared mailbox
if (credentials.useShared && credentials.userPrincipalName) {
apiUrl = `https://graph.microsoft.com/v1.0/users/${credentials.userPrincipalName}${resource}`;
apiUrl = `${graphEndpoint}/v1.0/users/${credentials.userPrincipalName}${resource}`;
}

const options: IRequestOptions = {
Expand Down

0 comments on commit 1bb8e1b

Please sign in to comment.