first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
IDataObject,
|
||||
JsonObject,
|
||||
IRequestOptions,
|
||||
IHttpRequestMethods,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeApiError, NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
export async function msGraphSecurityApiRequest(
|
||||
this: IExecuteFunctions,
|
||||
method: IHttpRequestMethods,
|
||||
endpoint: string,
|
||||
body: IDataObject = {},
|
||||
qs: IDataObject = {},
|
||||
headers: IDataObject = {},
|
||||
) {
|
||||
const credentials = await this.getCredentials<{
|
||||
oauthTokenData: {
|
||||
access_token: string;
|
||||
};
|
||||
graphApiBaseUrl?: string;
|
||||
}>('microsoftGraphSecurityOAuth2Api');
|
||||
|
||||
const {
|
||||
oauthTokenData: { access_token },
|
||||
} = credentials;
|
||||
|
||||
const baseUrl = (
|
||||
typeof credentials.graphApiBaseUrl === 'string' && credentials.graphApiBaseUrl !== ''
|
||||
? credentials.graphApiBaseUrl
|
||||
: 'https://graph.microsoft.com'
|
||||
).replace(/\/+$/, '');
|
||||
|
||||
const options: IRequestOptions = {
|
||||
headers: {
|
||||
Authorization: `Bearer ${access_token}`,
|
||||
},
|
||||
method,
|
||||
body,
|
||||
qs,
|
||||
uri: `${baseUrl}/v1.0/security${endpoint}`,
|
||||
json: true,
|
||||
};
|
||||
|
||||
if (!Object.keys(body).length) {
|
||||
delete options.body;
|
||||
}
|
||||
|
||||
if (!Object.keys(qs).length) {
|
||||
delete options.qs;
|
||||
}
|
||||
|
||||
if (Object.keys(headers).length) {
|
||||
options.headers = { ...options.headers, ...headers };
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.helpers.request(options);
|
||||
} catch (error) {
|
||||
const nestedMessage = error?.error?.error?.message;
|
||||
|
||||
if (nestedMessage?.startsWith('{"')) {
|
||||
error = JSON.parse(nestedMessage as string);
|
||||
}
|
||||
|
||||
if (nestedMessage?.startsWith('Http request failed with statusCode=BadRequest')) {
|
||||
error.error.error.message = 'Request failed with bad request';
|
||||
} else if (nestedMessage?.startsWith('Http request failed with')) {
|
||||
const stringified = nestedMessage?.split(': ').pop();
|
||||
if (stringified) {
|
||||
error = JSON.parse(stringified as string);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
typeof nestedMessage === 'string' &&
|
||||
['Invalid filter clause', 'Invalid ODATA query filter'].includes(nestedMessage)
|
||||
) {
|
||||
error.error.error.message +=
|
||||
' - Please check that your query parameter syntax is correct: https://docs.microsoft.com/en-us/graph/query-parameters#filter-parameter';
|
||||
}
|
||||
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject);
|
||||
}
|
||||
}
|
||||
|
||||
export function tolerateDoubleQuotes(filterQueryParameter: string) {
|
||||
return filterQueryParameter.replace(/"/g, "'");
|
||||
}
|
||||
|
||||
export function throwOnEmptyUpdate(this: IExecuteFunctions) {
|
||||
throw new NodeOperationError(this.getNode(), 'Please enter at least one field to update');
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"node": "n8n-nodes-base.microsoftGraphSecurity",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Development"],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/credentials/microsoft/"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.microsoftgraphsecurity/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
IDataObject,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeConnectionTypes } from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
secureScoreControlProfileFields,
|
||||
secureScoreControlProfileOperations,
|
||||
secureScoreFields,
|
||||
secureScoreOperations,
|
||||
} from './descriptions';
|
||||
import {
|
||||
msGraphSecurityApiRequest,
|
||||
throwOnEmptyUpdate,
|
||||
tolerateDoubleQuotes,
|
||||
} from './GenericFunctions';
|
||||
|
||||
export class MicrosoftGraphSecurity implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Microsoft Graph Security',
|
||||
name: 'microsoftGraphSecurity',
|
||||
icon: 'file:microsoftGraph.svg',
|
||||
group: ['transform'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Consume the Microsoft Graph Security API',
|
||||
defaults: {
|
||||
name: 'Microsoft Graph Security',
|
||||
},
|
||||
usableAsTool: true,
|
||||
inputs: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
credentials: [
|
||||
{
|
||||
name: 'microsoftGraphSecurityOAuth2Api',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'Secure Score',
|
||||
value: 'secureScore',
|
||||
},
|
||||
{
|
||||
name: 'Secure Score Control Profile',
|
||||
value: 'secureScoreControlProfile',
|
||||
},
|
||||
],
|
||||
default: 'secureScore',
|
||||
},
|
||||
...secureScoreOperations,
|
||||
...secureScoreFields,
|
||||
...secureScoreControlProfileOperations,
|
||||
...secureScoreControlProfileFields,
|
||||
],
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
const resource = this.getNodeParameter('resource', 0) as
|
||||
| 'secureScore'
|
||||
| 'secureScoreControlProfile';
|
||||
const operation = this.getNodeParameter('operation', 0) as 'get' | 'getAll' | 'update';
|
||||
|
||||
let responseData;
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
if (resource === 'secureScore') {
|
||||
// **********************************************************************
|
||||
// secureScore
|
||||
// **********************************************************************
|
||||
|
||||
if (operation === 'get') {
|
||||
// ----------------------------------------
|
||||
// secureScore: get
|
||||
// ----------------------------------------
|
||||
|
||||
// https://docs.microsoft.com/en-us/graph/api/securescore-get
|
||||
|
||||
const secureScoreId = this.getNodeParameter('secureScoreId', i);
|
||||
|
||||
responseData = await msGraphSecurityApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/secureScores/${secureScoreId}`,
|
||||
);
|
||||
delete responseData['@odata.context'];
|
||||
} else if (operation === 'getAll') {
|
||||
// ----------------------------------------
|
||||
// secureScore: getAll
|
||||
// ----------------------------------------
|
||||
|
||||
// https://docs.microsoft.com/en-us/graph/api/security-list-securescores
|
||||
|
||||
const qs: IDataObject = {};
|
||||
|
||||
const { filter, includeControlScores } = this.getNodeParameter('filters', i) as {
|
||||
filter?: string;
|
||||
includeControlScores?: boolean;
|
||||
};
|
||||
|
||||
if (filter) {
|
||||
qs.$filter = tolerateDoubleQuotes(filter);
|
||||
}
|
||||
|
||||
const returnAll = this.getNodeParameter('returnAll', 0);
|
||||
|
||||
if (!returnAll) {
|
||||
qs.$count = true;
|
||||
qs.$top = this.getNodeParameter('limit', 0);
|
||||
}
|
||||
|
||||
responseData = (await msGraphSecurityApiRequest
|
||||
.call(this, 'GET', '/secureScores', {}, qs)
|
||||
.then((response) => response.value)) as Array<{ controlScores: object[] }>;
|
||||
|
||||
if (!includeControlScores) {
|
||||
responseData = responseData.map(({ controlScores: _controlScores, ...rest }) => rest);
|
||||
}
|
||||
}
|
||||
} else if (resource === 'secureScoreControlProfile') {
|
||||
// **********************************************************************
|
||||
// secureScoreControlProfile
|
||||
// **********************************************************************
|
||||
|
||||
if (operation === 'get') {
|
||||
// ----------------------------------------
|
||||
// secureScoreControlProfile: get
|
||||
// ----------------------------------------
|
||||
|
||||
// https://docs.microsoft.com/en-us/graph/api/securescorecontrolprofile-get
|
||||
|
||||
const secureScoreControlProfileId = this.getNodeParameter(
|
||||
'secureScoreControlProfileId',
|
||||
i,
|
||||
);
|
||||
const endpoint = `/secureScoreControlProfiles/${secureScoreControlProfileId}`;
|
||||
|
||||
responseData = await msGraphSecurityApiRequest.call(this, 'GET', endpoint);
|
||||
delete responseData['@odata.context'];
|
||||
} else if (operation === 'getAll') {
|
||||
// ----------------------------------------
|
||||
// secureScoreControlProfile: getAll
|
||||
// ----------------------------------------
|
||||
|
||||
// https://docs.microsoft.com/en-us/graph/api/security-list-securescorecontrolprofiles
|
||||
|
||||
const qs: IDataObject = {};
|
||||
|
||||
const { filter } = this.getNodeParameter('filters', i) as { filter?: string };
|
||||
|
||||
if (filter) {
|
||||
qs.$filter = tolerateDoubleQuotes(filter);
|
||||
}
|
||||
|
||||
const returnAll = this.getNodeParameter('returnAll', 0);
|
||||
|
||||
if (!returnAll) {
|
||||
qs.$count = true;
|
||||
qs.$top = this.getNodeParameter('limit', 0);
|
||||
}
|
||||
|
||||
responseData = await msGraphSecurityApiRequest
|
||||
.call(this, 'GET', '/secureScoreControlProfiles', {}, qs)
|
||||
.then((response) => response.value);
|
||||
} else if (operation === 'update') {
|
||||
// ----------------------------------------
|
||||
// secureScoreControlProfile: update
|
||||
// ----------------------------------------
|
||||
|
||||
// https://docs.microsoft.com/en-us/graph/api/securescorecontrolprofile-update
|
||||
|
||||
const body: IDataObject = {
|
||||
vendorInformation: {
|
||||
provider: this.getNodeParameter('provider', i),
|
||||
vendor: this.getNodeParameter('vendor', i),
|
||||
},
|
||||
};
|
||||
|
||||
const updateFields = this.getNodeParameter('updateFields', i);
|
||||
|
||||
if (!Object.keys(updateFields).length) {
|
||||
throwOnEmptyUpdate.call(this);
|
||||
}
|
||||
|
||||
if (Object.keys(updateFields).length) {
|
||||
Object.assign(body, updateFields);
|
||||
}
|
||||
|
||||
const id = this.getNodeParameter('secureScoreControlProfileId', i);
|
||||
const endpoint = `/secureScoreControlProfiles/${id}`;
|
||||
const headers = { Prefer: 'return=representation' };
|
||||
|
||||
responseData = await msGraphSecurityApiRequest.call(
|
||||
this,
|
||||
'PATCH',
|
||||
endpoint,
|
||||
body,
|
||||
{},
|
||||
headers,
|
||||
);
|
||||
delete responseData['@odata.context'];
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({ error: error.message });
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
Array.isArray(responseData)
|
||||
? returnData.push(...(responseData as IDataObject[]))
|
||||
: returnData.push(responseData as IDataObject);
|
||||
}
|
||||
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
}
|
||||
}
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const secureScoreControlProfileOperations: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['secureScoreControlProfile'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
action: 'Get a secure score control profile',
|
||||
},
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getAll',
|
||||
action: 'Get many secure score control profiles',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
action: 'Update a secure score control profile',
|
||||
},
|
||||
],
|
||||
default: 'get',
|
||||
},
|
||||
];
|
||||
|
||||
export const secureScoreControlProfileFields: INodeProperties[] = [
|
||||
// ----------------------------------------
|
||||
// secureScore: get
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Secure Score Control Profile ID',
|
||||
name: 'secureScoreControlProfileId',
|
||||
description: 'ID of the secure score control profile to retrieve',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['secureScoreControlProfile'],
|
||||
operation: ['get'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// secureScoreControlProfile: getAll
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['secureScoreControlProfile'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 50,
|
||||
description: 'Max number of results to return',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 1000,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['secureScoreControlProfile'],
|
||||
operation: ['getAll'],
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filters',
|
||||
type: 'collection',
|
||||
default: {},
|
||||
placeholder: 'Add Filter',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['secureScoreControlProfile'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Filter Query Parameter',
|
||||
name: 'filter',
|
||||
description:
|
||||
'<a href="https://docs.microsoft.com/en-us/graph/query-parameters#filter-parameter">Query parameter</a> to filter results by',
|
||||
type: 'string',
|
||||
default: '',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
|
||||
placeholder: "startsWith(id, 'AATP')",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// secureScoreControlProfile: update
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Secure Score Control Profile ID',
|
||||
name: 'secureScoreControlProfileId',
|
||||
description: 'ID of the secure score control profile to update',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['secureScoreControlProfile'],
|
||||
operation: ['update'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Provider',
|
||||
name: 'provider',
|
||||
type: 'string',
|
||||
description: 'Name of the provider of the security product or service',
|
||||
default: '',
|
||||
placeholder: 'SecureScore',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['secureScoreControlProfile'],
|
||||
operation: ['update'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Vendor',
|
||||
name: 'vendor',
|
||||
type: 'string',
|
||||
description: 'Name of the vendor of the security product or service',
|
||||
default: '',
|
||||
placeholder: 'Microsoft',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['secureScoreControlProfile'],
|
||||
operation: ['update'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Update Fields',
|
||||
name: 'updateFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['secureScoreControlProfile'],
|
||||
operation: ['update'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'State',
|
||||
name: 'state',
|
||||
type: 'options',
|
||||
default: 'Default',
|
||||
description: 'Analyst driven setting on the control',
|
||||
options: [
|
||||
{
|
||||
name: 'Default',
|
||||
value: 'Default',
|
||||
},
|
||||
{
|
||||
name: 'Ignored',
|
||||
value: 'Ignored',
|
||||
},
|
||||
{
|
||||
name: 'Third Party',
|
||||
value: 'ThirdParty',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const secureScoreOperations: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['secureScore'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
action: 'Get a secure score',
|
||||
},
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getAll',
|
||||
action: 'Get many secure scores',
|
||||
},
|
||||
],
|
||||
default: 'get',
|
||||
},
|
||||
];
|
||||
|
||||
export const secureScoreFields: INodeProperties[] = [
|
||||
// ----------------------------------------
|
||||
// secureScore: get
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Secure Score ID',
|
||||
name: 'secureScoreId',
|
||||
description: 'ID of the secure score to retrieve',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['secureScore'],
|
||||
operation: ['get'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// secureScore: getAll
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['secureScore'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 50,
|
||||
description: 'Max number of results to return',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 1000,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['secureScore'],
|
||||
operation: ['getAll'],
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filters',
|
||||
type: 'collection',
|
||||
default: {},
|
||||
placeholder: 'Add Filter',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['secureScore'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Filter Query Parameter',
|
||||
name: 'filter',
|
||||
description:
|
||||
'<a href="https://docs.microsoft.com/en-us/graph/query-parameters#filter-parameter">Query parameter</a> to filter results by',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'currentScore eq 13',
|
||||
},
|
||||
{
|
||||
displayName: 'Include Control Scores',
|
||||
name: 'includeControlScores',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './SecureScoreDescription';
|
||||
export * from './SecureScoreControlProfileDescription';
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 374.85 330"><defs><style>.cls-1{fill:#0078d4}.cls-2{fill:#28a8ea}.cls-5{fill:#0364b8}</style></defs><path d="M271.13 0h-167.4a20 20 0 0 0-17.3 10L2.69 155a20 20 0 0 0 0 20l83.74 145a20 20 0 0 0 17.35 10h167.4a20 20 0 0 0 17.35-10l83.69-145a20 20 0 0 0 0-20L288.53 10a20 20 0 0 0-17.4-10" class="cls-1"/><path d="M372.17 155 288.43 10a19.9 19.9 0 0 0-6-6.5l6.77 161.17 85.63.27a20 20 0 0 0-2.66-9.94" class="cls-2"/><path d="M775.26 550 859 695a20.1 20.1 0 0 0 5.43 6.08l36.39-58.56-126.67-94.74a20 20 0 0 0 1.11 2.22" style="fill:#14447d" transform="translate(-772.57 -375)"/><path d="M86.43 10 2.69 155a19 19 0 0 0-1.46 3.12l120.66-95.83L91.54 4.16A19.9 19.9 0 0 0 86.43 10" class="cls-1"/><path d="M271.13 0h-167.4a20 20 0 0 0-12.19 4.14l30.35 58.13L275.8.56a20.2 20.2 0 0 0-4.67-.56M128.19 267.5l161.03-102.81L121.89 62.27z" class="cls-2"/><path d="m1048.37 375.56-153.91 61.71 167.33 102.42-6.79-161.17a20 20 0 0 0-6.63-2.96" style="fill:#50d9ff" transform="translate(-772.57 -375)"/><path d="M1.23 158.1a20.07 20.07 0 0 0 .35 14.68L128.2 267.5l-6.31-205.23Z" class="cls-5"/><path d="M864.38 701.06A20.07 20.07 0 0 0 876.3 705h167.4a20 20 0 0 0 3.8-.38L900.77 642.5Z" style="fill:#0f335e" transform="translate(-772.57 -375)"/><path d="m128.2 267.5 146.73 62.12a20 20 0 0 0 7.54-3.15l6.75-161.78Z" class="cls-5"/><path d="m289.22 164.69-6.79 161.78a19.9 19.9 0 0 0 6-6.49l83.7-145a20 20 0 0 0 2.68-10.06Z" class="cls-1"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,742 @@
|
||||
import { mockDeep } from 'jest-mock-extended';
|
||||
import type { IExecuteFunctions, INode } from 'n8n-workflow';
|
||||
import { NodeApiError, NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
msGraphSecurityApiRequest,
|
||||
tolerateDoubleQuotes,
|
||||
throwOnEmptyUpdate,
|
||||
} from '../GenericFunctions';
|
||||
|
||||
describe('Microsoft GraphSecurity GenericFunctions', () => {
|
||||
let mockExecuteFunctions: jest.Mocked<IExecuteFunctions>;
|
||||
let mockNode: INode;
|
||||
let mockRequest: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
mockExecuteFunctions = mockDeep<IExecuteFunctions>();
|
||||
mockRequest = jest.fn();
|
||||
mockExecuteFunctions.helpers.request = mockRequest;
|
||||
|
||||
mockNode = {
|
||||
id: 'test-node',
|
||||
name: 'Test GraphSecurity Node',
|
||||
type: 'n8n-nodes-base.microsoftGraphSecurity',
|
||||
typeVersion: 1,
|
||||
position: [0, 0],
|
||||
parameters: {},
|
||||
};
|
||||
mockExecuteFunctions.getNode.mockReturnValue(mockNode);
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('msGraphSecurityApiRequest', () => {
|
||||
const mockCredentials = {
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue(mockCredentials);
|
||||
});
|
||||
|
||||
describe('successful requests', () => {
|
||||
it('should make a successful GET request with default parameters', async () => {
|
||||
const mockResponse = { data: 'test data' };
|
||||
mockRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts');
|
||||
|
||||
expect(mockRequest).toHaveBeenCalledWith({
|
||||
headers: {
|
||||
Authorization: 'Bearer test-access-token',
|
||||
},
|
||||
method: 'GET',
|
||||
uri: 'https://graph.microsoft.com/v1.0/security/alerts',
|
||||
json: true,
|
||||
});
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
it('should make a POST request with body data', async () => {
|
||||
const mockResponse = { id: '123', status: 'created' };
|
||||
const requestBody = { name: 'Test Alert', status: 'active' };
|
||||
mockRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await msGraphSecurityApiRequest.call(
|
||||
mockExecuteFunctions,
|
||||
'POST',
|
||||
'/alerts',
|
||||
requestBody,
|
||||
);
|
||||
|
||||
expect(mockRequest).toHaveBeenCalledWith({
|
||||
headers: {
|
||||
Authorization: 'Bearer test-access-token',
|
||||
},
|
||||
method: 'POST',
|
||||
body: requestBody,
|
||||
uri: 'https://graph.microsoft.com/v1.0/security/alerts',
|
||||
json: true,
|
||||
});
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
it('should make a request with query string parameters', async () => {
|
||||
const mockResponse = { alerts: [] };
|
||||
const queryParams = { $filter: "status eq 'active'", $top: 10 };
|
||||
mockRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await msGraphSecurityApiRequest.call(
|
||||
mockExecuteFunctions,
|
||||
'GET',
|
||||
'/alerts',
|
||||
{},
|
||||
queryParams,
|
||||
);
|
||||
|
||||
expect(mockRequest).toHaveBeenCalledWith({
|
||||
headers: {
|
||||
Authorization: 'Bearer test-access-token',
|
||||
},
|
||||
method: 'GET',
|
||||
qs: queryParams,
|
||||
uri: 'https://graph.microsoft.com/v1.0/security/alerts',
|
||||
json: true,
|
||||
});
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
it('should make a request with custom headers', async () => {
|
||||
const mockResponse = { success: true };
|
||||
const customHeaders = { 'Content-Type': 'application/json', 'X-Custom-Header': 'test' };
|
||||
mockRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await msGraphSecurityApiRequest.call(
|
||||
mockExecuteFunctions,
|
||||
'PUT',
|
||||
'/secureScores',
|
||||
{ data: 'test' },
|
||||
{},
|
||||
customHeaders,
|
||||
);
|
||||
|
||||
expect(mockRequest).toHaveBeenCalledWith({
|
||||
headers: {
|
||||
Authorization: 'Bearer test-access-token',
|
||||
'Content-Type': 'application/json',
|
||||
'X-Custom-Header': 'test',
|
||||
},
|
||||
method: 'PUT',
|
||||
body: { data: 'test' },
|
||||
uri: 'https://graph.microsoft.com/v1.0/security/secureScores',
|
||||
json: true,
|
||||
});
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
it('should handle all parameters together', async () => {
|
||||
const mockResponse = { updated: true };
|
||||
const body = { status: 'resolved' };
|
||||
const qs = { $select: 'id,status' };
|
||||
const headers = { 'If-Match': 'etag-value' };
|
||||
mockRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await msGraphSecurityApiRequest.call(
|
||||
mockExecuteFunctions,
|
||||
'PATCH',
|
||||
'/alerts/123',
|
||||
body,
|
||||
qs,
|
||||
headers,
|
||||
);
|
||||
|
||||
expect(mockRequest).toHaveBeenCalledWith({
|
||||
headers: {
|
||||
Authorization: 'Bearer test-access-token',
|
||||
'If-Match': 'etag-value',
|
||||
},
|
||||
method: 'PATCH',
|
||||
body,
|
||||
qs,
|
||||
uri: 'https://graph.microsoft.com/v1.0/security/alerts/123',
|
||||
json: true,
|
||||
});
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
it('should remove empty body when no body data is provided', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
await msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts', {});
|
||||
|
||||
const requestOptions = mockRequest.mock.calls[0][0];
|
||||
expect(requestOptions.body).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should remove empty query string when no qs data is provided', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
await msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts', {}, {});
|
||||
|
||||
const requestOptions = mockRequest.mock.calls[0][0];
|
||||
expect(requestOptions.qs).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('credential handling', () => {
|
||||
it('should handle missing credentials', async () => {
|
||||
mockExecuteFunctions.getCredentials.mockRejectedValue(new Error('Credentials not found'));
|
||||
|
||||
await expect(
|
||||
msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts'),
|
||||
).rejects.toThrow('Credentials not found');
|
||||
});
|
||||
|
||||
it('should handle malformed credentials', async () => {
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {},
|
||||
} as any);
|
||||
mockRequest.mockResolvedValue({ data: 'test' });
|
||||
|
||||
const result = await msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts');
|
||||
|
||||
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' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('error handling', () => {
|
||||
it('should handle basic API errors', async () => {
|
||||
const apiError = {
|
||||
error: {
|
||||
error: {
|
||||
message: 'Resource not found',
|
||||
code: 'NotFound',
|
||||
},
|
||||
},
|
||||
};
|
||||
mockRequest.mockRejectedValue(apiError);
|
||||
|
||||
await expect(
|
||||
msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts/invalid-id'),
|
||||
).rejects.toThrow(NodeApiError);
|
||||
});
|
||||
|
||||
it('should parse JSON error messages', async () => {
|
||||
const jsonErrorMessage = '{"error":{"code":"InvalidRequest","message":"Invalid request"}}';
|
||||
const apiError = {
|
||||
error: {
|
||||
error: {
|
||||
message: jsonErrorMessage,
|
||||
},
|
||||
},
|
||||
};
|
||||
mockRequest.mockRejectedValue(apiError);
|
||||
|
||||
await expect(
|
||||
msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts'),
|
||||
).rejects.toThrow(NodeApiError);
|
||||
});
|
||||
|
||||
it('should handle BadRequest errors', async () => {
|
||||
const apiError = {
|
||||
error: {
|
||||
error: {
|
||||
message: 'Http request failed with statusCode=BadRequest: Invalid filter',
|
||||
},
|
||||
},
|
||||
};
|
||||
mockRequest.mockRejectedValue(apiError);
|
||||
|
||||
await expect(
|
||||
msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts'),
|
||||
).rejects.toThrow(NodeApiError);
|
||||
|
||||
expect(apiError.error.error.message).toBe('Request failed with bad request');
|
||||
});
|
||||
|
||||
it('should handle Http request failed errors with JSON content', async () => {
|
||||
const jsonError = '{"error":{"code":"Forbidden","message":"Insufficient privileges"}}';
|
||||
const apiError = {
|
||||
error: {
|
||||
error: {
|
||||
message: `Http request failed with statusCode=403: ${jsonError}`,
|
||||
},
|
||||
},
|
||||
};
|
||||
mockRequest.mockRejectedValue(apiError);
|
||||
|
||||
await expect(
|
||||
msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts'),
|
||||
).rejects.toThrow(NodeApiError);
|
||||
});
|
||||
|
||||
it('should handle Invalid filter clause errors', async () => {
|
||||
const apiError = {
|
||||
error: {
|
||||
error: {
|
||||
message: 'Invalid filter clause',
|
||||
},
|
||||
},
|
||||
};
|
||||
mockRequest.mockRejectedValue(apiError);
|
||||
|
||||
await expect(
|
||||
msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts'),
|
||||
).rejects.toThrow(NodeApiError);
|
||||
|
||||
expect(apiError.error.error.message).toContain(
|
||||
'Please check that your query parameter syntax is correct',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle Invalid ODATA query filter errors', async () => {
|
||||
const apiError = {
|
||||
error: {
|
||||
error: {
|
||||
message: 'Invalid ODATA query filter',
|
||||
},
|
||||
},
|
||||
};
|
||||
mockRequest.mockRejectedValue(apiError);
|
||||
|
||||
await expect(
|
||||
msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts'),
|
||||
).rejects.toThrow(NodeApiError);
|
||||
|
||||
expect(apiError.error.error.message).toContain(
|
||||
'Please check that your query parameter syntax is correct',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle errors without nested structure', async () => {
|
||||
const simpleError = {
|
||||
error: {
|
||||
error: {
|
||||
message: 'Simple network error',
|
||||
},
|
||||
},
|
||||
};
|
||||
mockRequest.mockRejectedValue(simpleError);
|
||||
|
||||
await expect(
|
||||
msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts'),
|
||||
).rejects.toThrow(NodeApiError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('endpoint construction', () => {
|
||||
it('should construct correct URL for different endpoints', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
await msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/secureScores');
|
||||
|
||||
expect(mockRequest).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
uri: 'https://graph.microsoft.com/v1.0/security/secureScores',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle endpoints with parameters', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
await msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts/123/comments');
|
||||
|
||||
expect(mockRequest).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
uri: 'https://graph.microsoft.com/v1.0/security/alerts/123/comments',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle endpoints without leading slash', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
await msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', 'alerts');
|
||||
|
||||
expect(mockRequest).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
uri: 'https://graph.microsoft.com/v1.0/securityalerts',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('graphApiBaseUrl from credentials', () => {
|
||||
it('should use base URL from credentials', 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 fall back to default when credentials.graphApiBaseUrl is empty', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequest.mockResolvedValue(mockResponse);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
graphApiBaseUrl: '',
|
||||
});
|
||||
|
||||
await msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts');
|
||||
|
||||
expect(mockRequest).toHaveBeenCalledWith({
|
||||
headers: {
|
||||
Authorization: 'Bearer test-access-token',
|
||||
},
|
||||
method: 'GET',
|
||||
uri: 'https://graph.microsoft.com/v1.0/security/alerts',
|
||||
json: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should fall back to default when credentials.graphApiBaseUrl is undefined', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequest.mockResolvedValue(mockResponse);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
});
|
||||
|
||||
await msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts');
|
||||
|
||||
expect(mockRequest).toHaveBeenCalledWith({
|
||||
headers: {
|
||||
Authorization: 'Bearer test-access-token',
|
||||
},
|
||||
method: 'GET',
|
||||
uri: 'https://graph.microsoft.com/v1.0/security/alerts',
|
||||
json: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should strip trailing slashes from base URL using regex', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequest.mockResolvedValue(mockResponse);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
graphApiBaseUrl: 'https://graph.microsoft.com/',
|
||||
});
|
||||
|
||||
await msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts');
|
||||
|
||||
expect(mockRequest).toHaveBeenCalledWith({
|
||||
headers: {
|
||||
Authorization: 'Bearer test-access-token',
|
||||
},
|
||||
method: 'GET',
|
||||
uri: 'https://graph.microsoft.com/v1.0/security/alerts',
|
||||
json: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should strip multiple trailing slashes from base URL', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequest.mockResolvedValue(mockResponse);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
graphApiBaseUrl: 'https://graph.microsoft.com///',
|
||||
});
|
||||
|
||||
await msGraphSecurityApiRequest.call(mockExecuteFunctions, 'GET', '/alerts');
|
||||
|
||||
expect(mockRequest).toHaveBeenCalledWith({
|
||||
headers: {
|
||||
Authorization: 'Bearer test-access-token',
|
||||
},
|
||||
method: 'GET',
|
||||
uri: 'https://graph.microsoft.com/v1.0/security/alerts',
|
||||
json: true,
|
||||
});
|
||||
});
|
||||
|
||||
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('tolerateDoubleQuotes', () => {
|
||||
it('should replace double quotes with single quotes', () => {
|
||||
const input = 'status eq "active" and severity eq "high"';
|
||||
const expected = "status eq 'active' and severity eq 'high'";
|
||||
|
||||
const result = tolerateDoubleQuotes(input);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle multiple double quotes', () => {
|
||||
const input = '"test" and "another" and "third"';
|
||||
const expected = "'test' and 'another' and 'third'";
|
||||
|
||||
const result = tolerateDoubleQuotes(input);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle empty string', () => {
|
||||
const input = '';
|
||||
const expected = '';
|
||||
|
||||
const result = tolerateDoubleQuotes(input);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle string with no double quotes', () => {
|
||||
const input = 'status eq active and severity eq high';
|
||||
const expected = 'status eq active and severity eq high';
|
||||
|
||||
const result = tolerateDoubleQuotes(input);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle string with only single quotes', () => {
|
||||
const input = "status eq 'active' and severity eq 'high'";
|
||||
const expected = "status eq 'active' and severity eq 'high'";
|
||||
|
||||
const result = tolerateDoubleQuotes(input);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle mixed quotes', () => {
|
||||
const input = 'status eq "active" and name eq \'test\' and type eq "alert"';
|
||||
const expected = "status eq 'active' and name eq 'test' and type eq 'alert'";
|
||||
|
||||
const result = tolerateDoubleQuotes(input);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle escaped quotes', () => {
|
||||
const input = 'description eq "He said \\"hello\\""';
|
||||
const expected = "description eq 'He said \\'hello\\''";
|
||||
|
||||
const result = tolerateDoubleQuotes(input);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle special characters within quotes', () => {
|
||||
const input = 'title eq "Alert: SQL Injection @#$%^&*()"';
|
||||
const expected = "title eq 'Alert: SQL Injection @#$%^&*()'";
|
||||
|
||||
const result = tolerateDoubleQuotes(input);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle very long strings', () => {
|
||||
const longString = '"' + 'a'.repeat(1000) + '"';
|
||||
const expectedString = "'" + 'a'.repeat(1000) + "'";
|
||||
|
||||
const result = tolerateDoubleQuotes(longString);
|
||||
|
||||
expect(result).toEqual(expectedString);
|
||||
});
|
||||
|
||||
it('should handle unicode characters', () => {
|
||||
const input = 'title eq "Alert: 测试 🚨 данные"';
|
||||
const expected = "title eq 'Alert: 测试 🚨 данные'";
|
||||
|
||||
const result = tolerateDoubleQuotes(input);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('throwOnEmptyUpdate', () => {
|
||||
it('should throw NodeOperationError with correct message', () => {
|
||||
expect(() => {
|
||||
throwOnEmptyUpdate.call(mockExecuteFunctions);
|
||||
}).toThrow(NodeOperationError);
|
||||
});
|
||||
|
||||
it('should throw with expected error message', () => {
|
||||
expect(() => {
|
||||
throwOnEmptyUpdate.call(mockExecuteFunctions);
|
||||
}).toThrow('Please enter at least one field to update');
|
||||
});
|
||||
|
||||
it('should use the correct node context', () => {
|
||||
try {
|
||||
throwOnEmptyUpdate.call(mockExecuteFunctions);
|
||||
} catch (error) {
|
||||
expect(mockExecuteFunctions.getNode).toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
|
||||
it('should always throw regardless of input', () => {
|
||||
expect(() => {
|
||||
throwOnEmptyUpdate.call(mockExecuteFunctions);
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge Cases and Integration', () => {
|
||||
beforeEach(() => {
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
const result = await msGraphSecurityApiRequest.call(
|
||||
mockExecuteFunctions,
|
||||
'GET',
|
||||
'/alerts',
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
);
|
||||
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
});
|
||||
});
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
describe('Test MicrosoftGraphSecurity, secureScore => get', () => {
|
||||
const credentials = {
|
||||
microsoftGraphSecurityOAuth2Api: {
|
||||
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.workflow.json'],
|
||||
});
|
||||
});
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"name": "Microsoft GraphSecurity SecureScore Get Test",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "trigger-id",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [820, 360]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"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": {
|
||||
"microsoftGraphSecurityOAuth2Api": {
|
||||
"id": "credential-id",
|
||||
"name": "Microsoft Graph Security 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": []
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
describe('Test MicrosoftGraphSecurity, secureScore => getAll', () => {
|
||||
const credentials = {
|
||||
microsoftGraphSecurityOAuth2Api: {
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
nock('https://graph.microsoft.com')
|
||||
.get('/v1.0/security/secureScores')
|
||||
.matchHeader('Authorization', 'Bearer test-access-token')
|
||||
.reply(200, {
|
||||
'@odata.context': 'https://graph.microsoft.com/v1.0/$metadata#security/secureScores',
|
||||
value: [
|
||||
{
|
||||
id: 'test-secure-score-1',
|
||||
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,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'test-secure-score-2',
|
||||
azureTenantId: 'tenant-456',
|
||||
activeUserCount: 200,
|
||||
createdDateTime: '2023-01-02T00:00:00Z',
|
||||
currentScore: 90,
|
||||
maxScore: 100,
|
||||
averageComparativeScores: [
|
||||
{
|
||||
basis: 'AllTenants',
|
||||
averageScore: 78.2,
|
||||
},
|
||||
],
|
||||
controlScores: [
|
||||
{
|
||||
controlName: 'Enable Conditional Access',
|
||||
controlCategory: 'Identity',
|
||||
score: 15,
|
||||
maxScore: 15,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['secureScore.getAll.workflow.json'],
|
||||
});
|
||||
});
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"name": "Microsoft GraphSecurity SecureScore GetAll Test",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "trigger-id",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [820, 360]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "secureScore",
|
||||
"operation": "getAll",
|
||||
"returnAll": true
|
||||
},
|
||||
"id": "node-id",
|
||||
"name": "Microsoft Graph Security",
|
||||
"type": "n8n-nodes-base.microsoftGraphSecurity",
|
||||
"typeVersion": 1,
|
||||
"position": [1040, 360],
|
||||
"credentials": {
|
||||
"microsoftGraphSecurityOAuth2Api": {
|
||||
"id": "credential-id",
|
||||
"name": "Microsoft Graph Security OAuth2"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Microsoft Graph Security": [
|
||||
{
|
||||
"json": {
|
||||
"id": "test-secure-score-1",
|
||||
"azureTenantId": "tenant-123",
|
||||
"activeUserCount": 100,
|
||||
"createdDateTime": "2023-01-01T00:00:00Z",
|
||||
"currentScore": 85,
|
||||
"maxScore": 100,
|
||||
"averageComparativeScores": [
|
||||
{
|
||||
"basis": "AllTenants",
|
||||
"averageScore": 75.5
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "test-secure-score-2",
|
||||
"azureTenantId": "tenant-456",
|
||||
"activeUserCount": 200,
|
||||
"createdDateTime": "2023-01-02T00:00:00Z",
|
||||
"currentScore": 90,
|
||||
"maxScore": 100,
|
||||
"averageComparativeScores": [
|
||||
{
|
||||
"basis": "AllTenants",
|
||||
"averageScore": 78.2
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"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": []
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
describe('Test MicrosoftGraphSecurity, secureScoreControlProfile => get', () => {
|
||||
const credentials = {
|
||||
microsoftGraphSecurityOAuth2Api: {
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
nock('https://graph.microsoft.com')
|
||||
.get('/v1.0/security/secureScoreControlProfiles/test-control-profile-id')
|
||||
.matchHeader('Authorization', 'Bearer test-access-token')
|
||||
.reply(200, {
|
||||
'@odata.context':
|
||||
'https://graph.microsoft.com/v1.0/$metadata#security/secureScoreControlProfiles/$entity',
|
||||
id: 'test-control-profile-id',
|
||||
azureTenantId: 'tenant-123',
|
||||
controlName: 'Enable multifactor authentication',
|
||||
controlCategory: 'Identity',
|
||||
actionType: 'Config',
|
||||
service: 'AAD',
|
||||
maxScore: 10,
|
||||
tier: 'Core',
|
||||
userImpact: 'Low',
|
||||
implementationCost: 'Low',
|
||||
rank: 1,
|
||||
threats: ['Account Breach', 'Credential Theft'],
|
||||
deprecated: false,
|
||||
remediation: 'Enable multi-factor authentication for all users',
|
||||
remediationImpact: 'Users will need to use an additional authentication method',
|
||||
actionUrl: 'https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/MFA',
|
||||
controlStateUpdates: [],
|
||||
vendorInformation: {
|
||||
provider: 'Microsoft',
|
||||
vendor: 'Microsoft',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['secureScoreControlProfile.get.workflow.json'],
|
||||
});
|
||||
});
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"name": "Microsoft GraphSecurity SecureScoreControlProfile Get Test",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "trigger-id",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [820, 360]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "secureScoreControlProfile",
|
||||
"operation": "get",
|
||||
"secureScoreControlProfileId": "test-control-profile-id"
|
||||
},
|
||||
"id": "node-id",
|
||||
"name": "Microsoft Graph Security",
|
||||
"type": "n8n-nodes-base.microsoftGraphSecurity",
|
||||
"typeVersion": 1,
|
||||
"position": [1040, 360],
|
||||
"credentials": {
|
||||
"microsoftGraphSecurityOAuth2Api": {
|
||||
"id": "credential-id",
|
||||
"name": "Microsoft Graph Security OAuth2"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Microsoft Graph Security": [
|
||||
{
|
||||
"json": {
|
||||
"id": "test-control-profile-id",
|
||||
"azureTenantId": "tenant-123",
|
||||
"controlName": "Enable multifactor authentication",
|
||||
"controlCategory": "Identity",
|
||||
"actionType": "Config",
|
||||
"service": "AAD",
|
||||
"maxScore": 10,
|
||||
"tier": "Core",
|
||||
"userImpact": "Low",
|
||||
"implementationCost": "Low",
|
||||
"rank": 1,
|
||||
"threats": ["Account Breach", "Credential Theft"],
|
||||
"deprecated": false,
|
||||
"remediation": "Enable multi-factor authentication for all users",
|
||||
"remediationImpact": "Users will need to use an additional authentication method",
|
||||
"actionUrl": "https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/MFA",
|
||||
"controlStateUpdates": [],
|
||||
"vendorInformation": {
|
||||
"provider": "Microsoft",
|
||||
"vendor": "Microsoft"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"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": []
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
describe('Test MicrosoftGraphSecurity, secureScoreControlProfile => getAll', () => {
|
||||
const credentials = {
|
||||
microsoftGraphSecurityOAuth2Api: {
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
nock('https://graph.microsoft.com')
|
||||
.get('/v1.0/security/secureScoreControlProfiles')
|
||||
.matchHeader('Authorization', 'Bearer test-access-token')
|
||||
.reply(200, {
|
||||
'@odata.context':
|
||||
'https://graph.microsoft.com/v1.0/$metadata#security/secureScoreControlProfiles',
|
||||
value: [
|
||||
{
|
||||
id: 'test-control-profile-1',
|
||||
azureTenantId: 'tenant-123',
|
||||
controlName: 'Enable multifactor authentication',
|
||||
controlCategory: 'Identity',
|
||||
actionType: 'Config',
|
||||
service: 'AAD',
|
||||
maxScore: 10,
|
||||
tier: 'Core',
|
||||
userImpact: 'Low',
|
||||
implementationCost: 'Low',
|
||||
rank: 1,
|
||||
threats: ['Account Breach', 'Credential Theft'],
|
||||
deprecated: false,
|
||||
remediation: 'Enable multi-factor authentication for all users',
|
||||
remediationImpact: 'Users will need to use an additional authentication method',
|
||||
actionUrl:
|
||||
'https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/MFA',
|
||||
controlStateUpdates: [],
|
||||
vendorInformation: {
|
||||
provider: 'Microsoft',
|
||||
vendor: 'Microsoft',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'test-control-profile-2',
|
||||
azureTenantId: 'tenant-456',
|
||||
controlName: 'Enable conditional access',
|
||||
controlCategory: 'Identity',
|
||||
actionType: 'Config',
|
||||
service: 'AAD',
|
||||
maxScore: 15,
|
||||
tier: 'Core',
|
||||
userImpact: 'Medium',
|
||||
implementationCost: 'Medium',
|
||||
rank: 2,
|
||||
threats: ['Account Breach', 'Data Exfiltration'],
|
||||
deprecated: false,
|
||||
remediation: 'Configure conditional access policies',
|
||||
remediationImpact: 'Users may need to authenticate differently based on location',
|
||||
actionUrl:
|
||||
'https://portal.azure.com/#blade/Microsoft_AAD_ConditionalAccess/ConditionalAccessBlade',
|
||||
controlStateUpdates: [],
|
||||
vendorInformation: {
|
||||
provider: 'Microsoft',
|
||||
vendor: 'Microsoft',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['secureScoreControlProfile.getAll.workflow.json'],
|
||||
});
|
||||
});
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
{
|
||||
"name": "Microsoft GraphSecurity SecureScoreControlProfile GetAll Test",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "trigger-id",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [820, 360]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "secureScoreControlProfile",
|
||||
"operation": "getAll",
|
||||
"returnAll": true
|
||||
},
|
||||
"id": "node-id",
|
||||
"name": "Microsoft Graph Security",
|
||||
"type": "n8n-nodes-base.microsoftGraphSecurity",
|
||||
"typeVersion": 1,
|
||||
"position": [1040, 360],
|
||||
"credentials": {
|
||||
"microsoftGraphSecurityOAuth2Api": {
|
||||
"id": "credential-id",
|
||||
"name": "Microsoft Graph Security OAuth2"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Microsoft Graph Security": [
|
||||
{
|
||||
"json": {
|
||||
"id": "test-control-profile-1",
|
||||
"azureTenantId": "tenant-123",
|
||||
"controlName": "Enable multifactor authentication",
|
||||
"controlCategory": "Identity",
|
||||
"actionType": "Config",
|
||||
"service": "AAD",
|
||||
"maxScore": 10,
|
||||
"tier": "Core",
|
||||
"userImpact": "Low",
|
||||
"implementationCost": "Low",
|
||||
"rank": 1,
|
||||
"threats": ["Account Breach", "Credential Theft"],
|
||||
"deprecated": false,
|
||||
"remediation": "Enable multi-factor authentication for all users",
|
||||
"remediationImpact": "Users will need to use an additional authentication method",
|
||||
"actionUrl": "https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/MFA",
|
||||
"controlStateUpdates": [],
|
||||
"vendorInformation": {
|
||||
"provider": "Microsoft",
|
||||
"vendor": "Microsoft"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "test-control-profile-2",
|
||||
"azureTenantId": "tenant-456",
|
||||
"controlName": "Enable conditional access",
|
||||
"controlCategory": "Identity",
|
||||
"actionType": "Config",
|
||||
"service": "AAD",
|
||||
"maxScore": 15,
|
||||
"tier": "Core",
|
||||
"userImpact": "Medium",
|
||||
"implementationCost": "Medium",
|
||||
"rank": 2,
|
||||
"threats": ["Account Breach", "Data Exfiltration"],
|
||||
"deprecated": false,
|
||||
"remediation": "Configure conditional access policies",
|
||||
"remediationImpact": "Users may need to authenticate differently based on location",
|
||||
"actionUrl": "https://portal.azure.com/#blade/Microsoft_AAD_ConditionalAccess/ConditionalAccessBlade",
|
||||
"controlStateUpdates": [],
|
||||
"vendorInformation": {
|
||||
"provider": "Microsoft",
|
||||
"vendor": "Microsoft"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"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": []
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
describe('Test MicrosoftGraphSecurity, secureScoreControlProfile => update', () => {
|
||||
const credentials = {
|
||||
microsoftGraphSecurityOAuth2Api: {
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
nock('https://graph.microsoft.com')
|
||||
.patch('/v1.0/security/secureScoreControlProfiles/test-control-profile-id', {
|
||||
vendorInformation: {
|
||||
provider: 'Microsoft',
|
||||
vendor: 'Microsoft',
|
||||
},
|
||||
state: 'Ignored',
|
||||
})
|
||||
.matchHeader('Authorization', 'Bearer test-access-token')
|
||||
.matchHeader('Prefer', 'return=representation')
|
||||
.reply(200, {
|
||||
'@odata.context':
|
||||
'https://graph.microsoft.com/v1.0/$metadata#security/secureScoreControlProfiles/$entity',
|
||||
id: 'test-control-profile-id',
|
||||
azureTenantId: 'tenant-123',
|
||||
controlName: 'Enable multifactor authentication',
|
||||
controlCategory: 'Identity',
|
||||
actionType: 'Config',
|
||||
service: 'AAD',
|
||||
maxScore: 10,
|
||||
tier: 'Core',
|
||||
userImpact: 'Low',
|
||||
implementationCost: 'Low',
|
||||
rank: 1,
|
||||
threats: ['Account Breach', 'Credential Theft'],
|
||||
deprecated: false,
|
||||
remediation: 'Enable multi-factor authentication for all users',
|
||||
remediationImpact: 'Users will need to use an additional authentication method',
|
||||
actionUrl: 'https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/MFA',
|
||||
controlStateUpdates: [],
|
||||
state: 'Ignored',
|
||||
vendorInformation: {
|
||||
provider: 'Microsoft',
|
||||
vendor: 'Microsoft',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['secureScoreControlProfile.update.workflow.json'],
|
||||
});
|
||||
});
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"name": "Microsoft GraphSecurity SecureScoreControlProfile Update Test",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "trigger-id",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [820, 360]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "secureScoreControlProfile",
|
||||
"operation": "update",
|
||||
"secureScoreControlProfileId": "test-control-profile-id",
|
||||
"provider": "Microsoft",
|
||||
"vendor": "Microsoft",
|
||||
"updateFields": {
|
||||
"state": "Ignored"
|
||||
}
|
||||
},
|
||||
"id": "node-id",
|
||||
"name": "Microsoft Graph Security",
|
||||
"type": "n8n-nodes-base.microsoftGraphSecurity",
|
||||
"typeVersion": 1,
|
||||
"position": [1040, 360],
|
||||
"credentials": {
|
||||
"microsoftGraphSecurityOAuth2Api": {
|
||||
"id": "credential-id",
|
||||
"name": "Microsoft Graph Security OAuth2"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Microsoft Graph Security": [
|
||||
{
|
||||
"json": {
|
||||
"id": "test-control-profile-id",
|
||||
"azureTenantId": "tenant-123",
|
||||
"controlName": "Enable multifactor authentication",
|
||||
"controlCategory": "Identity",
|
||||
"actionType": "Config",
|
||||
"service": "AAD",
|
||||
"maxScore": 10,
|
||||
"tier": "Core",
|
||||
"userImpact": "Low",
|
||||
"implementationCost": "Low",
|
||||
"rank": 1,
|
||||
"threats": ["Account Breach", "Credential Theft"],
|
||||
"deprecated": false,
|
||||
"remediation": "Enable multi-factor authentication for all users",
|
||||
"remediationImpact": "Users will need to use an additional authentication method",
|
||||
"actionUrl": "https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/MFA",
|
||||
"controlStateUpdates": [],
|
||||
"state": "Ignored",
|
||||
"vendorInformation": {
|
||||
"provider": "Microsoft",
|
||||
"vendor": "Microsoft"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"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": []
|
||||
}
|
||||
Reference in New Issue
Block a user