mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 05:29:44 +02:00
feat(Microsoft Graph Security Node): Support authenticating with the generic Microsoft OAuth2 credential (#32529)
This commit is contained in:
parent
7416a23d04
commit
c2a006aab8
|
|
@ -7,6 +7,23 @@ import type {
|
|||
} from 'n8n-workflow';
|
||||
import { NodeApiError, NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
export type GraphSecurityCredentialType = 'microsoftGraphSecurityOAuth2Api' | 'microsoftOAuth2Api';
|
||||
|
||||
/**
|
||||
* Resolves which credential type the node is configured to use. Defaults to the
|
||||
* node-specific `microsoftGraphSecurityOAuth2Api` so existing workflows (and
|
||||
* nodes without an explicit `authentication` selection) keep working unchanged,
|
||||
* while allowing the generic `microsoftOAuth2Api` (Graph) credential to be
|
||||
* selected.
|
||||
*/
|
||||
export function getGraphSecurityCredentialType(
|
||||
this: IExecuteFunctions,
|
||||
): GraphSecurityCredentialType {
|
||||
return this.getNodeParameter('authentication', 0) === 'microsoftOAuth2Api'
|
||||
? 'microsoftOAuth2Api'
|
||||
: 'microsoftGraphSecurityOAuth2Api';
|
||||
}
|
||||
|
||||
export async function msGraphSecurityApiRequest(
|
||||
this: IExecuteFunctions,
|
||||
method: IHttpRequestMethods,
|
||||
|
|
@ -15,17 +32,26 @@ export async function msGraphSecurityApiRequest(
|
|||
qs: IDataObject = {},
|
||||
headers: IDataObject = {},
|
||||
) {
|
||||
const credentialType = getGraphSecurityCredentialType.call(this);
|
||||
|
||||
const credentials = await this.getCredentials<{
|
||||
oauthTokenData: {
|
||||
access_token: string;
|
||||
};
|
||||
graphApiBaseUrl?: string;
|
||||
}>('microsoftGraphSecurityOAuth2Api');
|
||||
}>(credentialType);
|
||||
|
||||
const {
|
||||
oauthTokenData: { access_token },
|
||||
} = credentials;
|
||||
|
||||
if (!access_token) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
'No access token found in the credential. Reconnect the OAuth2 credential.',
|
||||
);
|
||||
}
|
||||
|
||||
const baseUrl = (
|
||||
typeof credentials.graphApiBaseUrl === 'string' && credentials.graphApiBaseUrl !== ''
|
||||
? credentials.graphApiBaseUrl
|
||||
|
|
|
|||
|
|
@ -38,9 +38,42 @@ export class MicrosoftGraphSecurity implements INodeType {
|
|||
{
|
||||
name: 'microsoftGraphSecurityOAuth2Api',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: ['microsoftGraphSecurityOAuth2Api'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'microsoftOAuth2Api',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: ['microsoftOAuth2Api'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
name: 'authentication',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'Graph Security OAuth2',
|
||||
value: 'microsoftGraphSecurityOAuth2Api',
|
||||
},
|
||||
{
|
||||
name: 'Microsoft OAuth2 (Graph)',
|
||||
value: 'microsoftOAuth2Api',
|
||||
description:
|
||||
'Generic Microsoft Graph credential. It must have the SecurityEvents.ReadWrite.All offline_access scope with Entra admin consent.',
|
||||
},
|
||||
],
|
||||
default: 'microsoftGraphSecurityOAuth2Api',
|
||||
},
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
|
|
|
|||
|
|
@ -29,6 +29,13 @@ describe('Microsoft GraphSecurity GenericFunctions', () => {
|
|||
};
|
||||
mockExecuteFunctions.getNode.mockReturnValue(mockNode);
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Register this AFTER clearAllMocks so it survives. Unstubbed getNodeParameter returns its
|
||||
// own 3rd-arg default (or undefined) instead of a truthy deep-mock proxy, so a node with no
|
||||
// explicit authentication resolves to the legacy credential for the right reason: the
|
||||
// credential-type resolver only switches to the generic credential on an exact
|
||||
// 'microsoftOAuth2Api' match, everything else (incl. undefined) falls back to legacy.
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((_name, _i, fallback) => fallback);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
@ -193,31 +200,35 @@ describe('Microsoft GraphSecurity GenericFunctions', () => {
|
|||
});
|
||||
|
||||
describe('credential handling', () => {
|
||||
it('should handle missing credentials', async () => {
|
||||
it('propagates credential-resolution failures without issuing a request', async () => {
|
||||
mockExecuteFunctions.getCredentials.mockRejectedValue(new Error('Credentials not found'));
|
||||
|
||||
await expect(
|
||||
msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts'),
|
||||
).rejects.toThrow('Credentials not found');
|
||||
|
||||
expect(mockExecuteFunctions.getCredentials).toHaveBeenCalledTimes(1);
|
||||
expect(mockExecuteFunctions.getCredentials).toHaveBeenCalledWith(
|
||||
'microsoftGraphSecurityOAuth2Api',
|
||||
);
|
||||
expect(mockRequest).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle malformed credentials', async () => {
|
||||
it('throws a NodeOperationError when the credential has no access token', async () => {
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {},
|
||||
} as any);
|
||||
mockRequest.mockResolvedValue({ data: 'test' });
|
||||
|
||||
const result = await msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts');
|
||||
await expect(
|
||||
msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts'),
|
||||
).rejects.toThrow(NodeOperationError);
|
||||
|
||||
expect(mockRequest).toHaveBeenCalledWith({
|
||||
headers: {
|
||||
Authorization: 'Bearer undefined',
|
||||
},
|
||||
method: 'GET',
|
||||
uri: 'https://graph.microsoft.com/v1.0/security/alerts',
|
||||
json: true,
|
||||
});
|
||||
expect(result).toEqual({ data: 'test' });
|
||||
await expect(
|
||||
msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts'),
|
||||
).rejects.toThrow('No access token found in the credential');
|
||||
|
||||
expect(mockRequest).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -264,11 +275,12 @@ describe('Microsoft GraphSecurity GenericFunctions', () => {
|
|||
};
|
||||
mockRequest.mockRejectedValue(apiError);
|
||||
|
||||
await expect(
|
||||
msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts'),
|
||||
).rejects.toThrow(NodeApiError);
|
||||
const thrown: unknown = await msGraphSecurityApiRequest
|
||||
.call(mockExecuteFunctions, 'GET', '/alerts')
|
||||
.catch((error: unknown) => error);
|
||||
|
||||
expect(apiError.error.error.message).toBe('Request failed with bad request');
|
||||
expect(thrown).toBeInstanceOf(NodeApiError);
|
||||
expect((thrown as NodeApiError).message).toBe('Request failed with bad request');
|
||||
});
|
||||
|
||||
it('should handle Http request failed errors with JSON content', async () => {
|
||||
|
|
@ -297,11 +309,12 @@ describe('Microsoft GraphSecurity GenericFunctions', () => {
|
|||
};
|
||||
mockRequest.mockRejectedValue(apiError);
|
||||
|
||||
await expect(
|
||||
msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts'),
|
||||
).rejects.toThrow(NodeApiError);
|
||||
const thrown: unknown = await msGraphSecurityApiRequest
|
||||
.call(mockExecuteFunctions, 'GET', '/alerts')
|
||||
.catch((error: unknown) => error);
|
||||
|
||||
expect(apiError.error.error.message).toContain(
|
||||
expect(thrown).toBeInstanceOf(NodeApiError);
|
||||
expect((thrown as NodeApiError).message).toContain(
|
||||
'Please check that your query parameter syntax is correct',
|
||||
);
|
||||
});
|
||||
|
|
@ -316,11 +329,12 @@ describe('Microsoft GraphSecurity GenericFunctions', () => {
|
|||
};
|
||||
mockRequest.mockRejectedValue(apiError);
|
||||
|
||||
await expect(
|
||||
msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts'),
|
||||
).rejects.toThrow(NodeApiError);
|
||||
const thrown: unknown = await msGraphSecurityApiRequest
|
||||
.call(mockExecuteFunctions, 'GET', '/alerts')
|
||||
.catch((error: unknown) => error);
|
||||
|
||||
expect(apiError.error.error.message).toContain(
|
||||
expect(thrown).toBeInstanceOf(NodeApiError);
|
||||
expect((thrown as NodeApiError).message).toContain(
|
||||
'Please check that your query parameter syntax is correct',
|
||||
);
|
||||
});
|
||||
|
|
@ -557,6 +571,181 @@ describe('Microsoft GraphSecurity GenericFunctions', () => {
|
|||
json: true,
|
||||
});
|
||||
});
|
||||
|
||||
describe('generic OAuth2 branch', () => {
|
||||
beforeEach(() => {
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((name, _i, fallback) =>
|
||||
name === 'authentication' ? 'microsoftOAuth2Api' : fallback,
|
||||
);
|
||||
});
|
||||
|
||||
it('should use US Government cloud endpoint', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequest.mockResolvedValue(mockResponse);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
graphApiBaseUrl: 'https://graph.microsoft.us',
|
||||
});
|
||||
|
||||
await msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts');
|
||||
|
||||
expect(mockRequest).toHaveBeenCalledWith({
|
||||
headers: {
|
||||
Authorization: 'Bearer test-access-token',
|
||||
},
|
||||
method: 'GET',
|
||||
uri: 'https://graph.microsoft.us/v1.0/security/alerts',
|
||||
json: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should use US Government DOD cloud endpoint', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequest.mockResolvedValue(mockResponse);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
graphApiBaseUrl: 'https://dod-graph.microsoft.us',
|
||||
});
|
||||
|
||||
await msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts');
|
||||
|
||||
expect(mockRequest).toHaveBeenCalledWith({
|
||||
headers: {
|
||||
Authorization: 'Bearer test-access-token',
|
||||
},
|
||||
method: 'GET',
|
||||
uri: 'https://dod-graph.microsoft.us/v1.0/security/alerts',
|
||||
json: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should use China cloud endpoint', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequest.mockResolvedValue(mockResponse);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
graphApiBaseUrl: 'https://microsoftgraph.chinacloudapi.cn',
|
||||
});
|
||||
|
||||
await msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts');
|
||||
|
||||
expect(mockRequest).toHaveBeenCalledWith({
|
||||
headers: {
|
||||
Authorization: 'Bearer test-access-token',
|
||||
},
|
||||
method: 'GET',
|
||||
uri: 'https://microsoftgraph.chinacloudapi.cn/v1.0/security/alerts',
|
||||
json: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('credential resolution', () => {
|
||||
it('should use the legacy credential by default (backfill/fallback contract)', async () => {
|
||||
mockRequest.mockResolvedValue({ data: 'test' });
|
||||
|
||||
await msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts');
|
||||
|
||||
expect(mockExecuteFunctions.getCredentials).toHaveBeenCalledTimes(1);
|
||||
expect(mockExecuteFunctions.getCredentials).toHaveBeenCalledWith(
|
||||
'microsoftGraphSecurityOAuth2Api',
|
||||
);
|
||||
});
|
||||
|
||||
it('should use the legacy credential when authentication is microsoftGraphSecurityOAuth2Api', async () => {
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((name, _i, fallback) =>
|
||||
name === 'authentication' ? 'microsoftGraphSecurityOAuth2Api' : fallback,
|
||||
);
|
||||
mockRequest.mockResolvedValue({ data: 'test' });
|
||||
|
||||
await msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts');
|
||||
|
||||
expect(mockExecuteFunctions.getCredentials).toHaveBeenCalledTimes(1);
|
||||
expect(mockExecuteFunctions.getCredentials).toHaveBeenCalledWith(
|
||||
'microsoftGraphSecurityOAuth2Api',
|
||||
);
|
||||
});
|
||||
|
||||
it('should use the generic credential when authentication is microsoftOAuth2Api', async () => {
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((name, _i, fallback) =>
|
||||
name === 'authentication' ? 'microsoftOAuth2Api' : fallback,
|
||||
);
|
||||
mockRequest.mockResolvedValue({ data: 'test' });
|
||||
|
||||
await msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts');
|
||||
|
||||
expect(mockExecuteFunctions.getCredentials).toHaveBeenCalledTimes(1);
|
||||
expect(mockExecuteFunctions.getCredentials).toHaveBeenCalledWith('microsoftOAuth2Api');
|
||||
});
|
||||
|
||||
it('resolves the legacy credential for a saved node with no authentication param', async () => {
|
||||
mockRequest.mockResolvedValue({ data: 'test' });
|
||||
|
||||
await expect(
|
||||
msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts'),
|
||||
).resolves.not.toThrow();
|
||||
|
||||
expect(mockExecuteFunctions.getCredentials).toHaveBeenCalledWith(
|
||||
'microsoftGraphSecurityOAuth2Api',
|
||||
);
|
||||
});
|
||||
|
||||
it('resolves the legacy credential for any unrecognized authentication value', async () => {
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((name, _i, fallback) =>
|
||||
name === 'authentication' ? 'somethingElse' : fallback,
|
||||
);
|
||||
mockRequest.mockResolvedValue({ data: 'test' });
|
||||
|
||||
await msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts');
|
||||
|
||||
expect(mockExecuteFunctions.getCredentials).toHaveBeenCalledWith(
|
||||
'microsoftGraphSecurityOAuth2Api',
|
||||
);
|
||||
});
|
||||
|
||||
it('resolves the legacy credential when the resolved authentication value is undefined', async () => {
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((name, _i, fallback) =>
|
||||
name === 'authentication' ? undefined : fallback,
|
||||
);
|
||||
mockRequest.mockResolvedValue({ data: 'test' });
|
||||
|
||||
await msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts');
|
||||
|
||||
expect(mockExecuteFunctions.getCredentials).toHaveBeenCalledWith(
|
||||
'microsoftGraphSecurityOAuth2Api',
|
||||
);
|
||||
});
|
||||
|
||||
it('should build a default-base-URL request on the generic branch', async () => {
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((name, _i, fallback) =>
|
||||
name === 'authentication' ? 'microsoftOAuth2Api' : fallback,
|
||||
);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
});
|
||||
mockRequest.mockResolvedValue({ data: 'test' });
|
||||
|
||||
await msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts');
|
||||
|
||||
expect(mockExecuteFunctions.getCredentials).toHaveBeenCalledWith('microsoftOAuth2Api');
|
||||
expect(mockRequest).toHaveBeenCalledWith({
|
||||
headers: {
|
||||
Authorization: 'Bearer test-access-token',
|
||||
},
|
||||
method: 'GET',
|
||||
uri: 'https://graph.microsoft.com/v1.0/security/alerts',
|
||||
json: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -666,11 +855,11 @@ describe('Microsoft GraphSecurity GenericFunctions', () => {
|
|||
});
|
||||
|
||||
it('should use the correct node context', () => {
|
||||
try {
|
||||
expect(() => {
|
||||
throwOnEmptyUpdate.call(mockExecuteFunctions);
|
||||
} catch (error) {
|
||||
expect(mockExecuteFunctions.getNode).toHaveBeenCalled();
|
||||
}
|
||||
}).toThrow();
|
||||
|
||||
expect(mockExecuteFunctions.getNode).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should always throw regardless of input', () => {
|
||||
|
|
@ -689,41 +878,6 @@ describe('Microsoft GraphSecurity GenericFunctions', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should handle concurrent requests', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const promises: Array<Promise<any>> = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
promises.push(msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts/' + i));
|
||||
}
|
||||
|
||||
const results = await Promise.all(promises);
|
||||
|
||||
expect(results).toHaveLength(5);
|
||||
expect(mockRequest).toHaveBeenCalledTimes(5);
|
||||
});
|
||||
|
||||
it('should handle extremely large request bodies', async () => {
|
||||
const mockResponse = { success: true };
|
||||
const largeBody = { data: 'x'.repeat(10000) };
|
||||
mockRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await msGraphSecurityApiRequest.call(
|
||||
mockExecuteFunctions,
|
||||
'POST',
|
||||
'/alerts',
|
||||
largeBody,
|
||||
);
|
||||
|
||||
expect(result).toEqual(mockResponse);
|
||||
expect(mockRequest).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
body: largeBody,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle empty parameters gracefully', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequest.mockResolvedValue(mockResponse);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
import { MicrosoftGraphSecurity } from '../MicrosoftGraphSecurity.node';
|
||||
|
||||
describe('MicrosoftGraphSecurity node description', () => {
|
||||
const node = new MicrosoftGraphSecurity();
|
||||
|
||||
const authProp = node.description.properties.find((p) => p.name === 'authentication');
|
||||
const credentials = node.description.credentials ?? [];
|
||||
const legacyCred = credentials.find((c) => c.name === 'microsoftGraphSecurityOAuth2Api');
|
||||
const genericCred = credentials.find((c) => c.name === 'microsoftOAuth2Api');
|
||||
|
||||
it('defaults authentication to the legacy gate value', () => {
|
||||
expect(authProp?.default).toBe('microsoftGraphSecurityOAuth2Api');
|
||||
});
|
||||
|
||||
it('gates the legacy credential on exactly the authentication default', () => {
|
||||
// Reference the property default so the credential gate and the default cannot drift.
|
||||
expect(legacyCred?.displayOptions?.show?.authentication).toContain(authProp?.default);
|
||||
});
|
||||
|
||||
it('gates the generic credential on microsoftOAuth2Api', () => {
|
||||
expect(genericCred?.displayOptions?.show?.authentication).toContain('microsoftOAuth2Api');
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
describe('Test MicrosoftGraphSecurity, secureScore => get (generic OAuth2)', () => {
|
||||
const credentials = {
|
||||
microsoftOAuth2Api: {
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
nock('https://graph.microsoft.com')
|
||||
.get('/v1.0/security/secureScores/test-secure-score-id')
|
||||
.matchHeader('Authorization', 'Bearer test-access-token')
|
||||
.reply(200, {
|
||||
'@odata.context':
|
||||
'https://graph.microsoft.com/v1.0/$metadata#security/secureScores/$entity',
|
||||
id: 'test-secure-score-id',
|
||||
azureTenantId: 'tenant-123',
|
||||
activeUserCount: 100,
|
||||
createdDateTime: '2023-01-01T00:00:00Z',
|
||||
currentScore: 85,
|
||||
maxScore: 100,
|
||||
averageComparativeScores: [
|
||||
{
|
||||
basis: 'AllTenants',
|
||||
averageScore: 75.5,
|
||||
},
|
||||
],
|
||||
controlScores: [
|
||||
{
|
||||
controlName: 'Enable MFA',
|
||||
controlCategory: 'Identity',
|
||||
score: 10,
|
||||
maxScore: 10,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['secureScore.get.genericOAuth2.workflow.json'],
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
{
|
||||
"name": "Microsoft GraphSecurity SecureScore Get Generic OAuth2 Test",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "trigger-id",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [820, 360]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "microsoftOAuth2Api",
|
||||
"resource": "secureScore",
|
||||
"operation": "get",
|
||||
"secureScoreId": "test-secure-score-id"
|
||||
},
|
||||
"id": "node-id",
|
||||
"name": "Microsoft Graph Security",
|
||||
"type": "n8n-nodes-base.microsoftGraphSecurity",
|
||||
"typeVersion": 1,
|
||||
"position": [1040, 360],
|
||||
"credentials": {
|
||||
"microsoftOAuth2Api": {
|
||||
"id": "credential-id",
|
||||
"name": "Microsoft OAuth2"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Microsoft Graph Security": [
|
||||
{
|
||||
"json": {
|
||||
"id": "test-secure-score-id",
|
||||
"azureTenantId": "tenant-123",
|
||||
"activeUserCount": 100,
|
||||
"createdDateTime": "2023-01-01T00:00:00Z",
|
||||
"currentScore": 85,
|
||||
"maxScore": 100,
|
||||
"averageComparativeScores": [
|
||||
{
|
||||
"basis": "AllTenants",
|
||||
"averageScore": 75.5
|
||||
}
|
||||
],
|
||||
"controlScores": [
|
||||
{
|
||||
"controlName": "Enable MFA",
|
||||
"controlCategory": "Identity",
|
||||
"score": 10,
|
||||
"maxScore": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Microsoft Graph Security",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"versionId": "test-version-id",
|
||||
"id": "test-workflow-id",
|
||||
"meta": {
|
||||
"instanceId": "test-instance-id"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user