mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
feat(AWS IAM Node): Add new AWS IAM Node (#11963)
Co-authored-by: Adina Totorean <adinatotorean99@gmail.com>
This commit is contained in:
18
packages/nodes-base/nodes/Aws/IAM/AwsIam.node.json
Normal file
18
packages/nodes-base/nodes/Aws/IAM/AwsIam.node.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"node": "n8n-nodes-base.awsiam",
|
||||||
|
"nodeVersion": "1.0",
|
||||||
|
"codexVersion": "1.0",
|
||||||
|
"categories": ["Development"],
|
||||||
|
"resources": {
|
||||||
|
"credentialDocumentation": [
|
||||||
|
{
|
||||||
|
"url": "https://docs.n8n.io/integrations/builtin/credentials/aws/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryDocumentation": [
|
||||||
|
{
|
||||||
|
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.awsiam/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
69
packages/nodes-base/nodes/Aws/IAM/AwsIam.node.ts
Normal file
69
packages/nodes-base/nodes/Aws/IAM/AwsIam.node.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
|
||||||
|
import { NodeConnectionTypes } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { user, group } from './descriptions';
|
||||||
|
import { BASE_URL } from './helpers/constants';
|
||||||
|
import { encodeBodyAsFormUrlEncoded } from './helpers/utils';
|
||||||
|
import { searchGroups, searchUsers, searchGroupsForUser } from './methods/listSearch';
|
||||||
|
|
||||||
|
export class AwsIam implements INodeType {
|
||||||
|
description: INodeTypeDescription = {
|
||||||
|
displayName: 'AWS IAM',
|
||||||
|
name: 'awsIam',
|
||||||
|
icon: 'file:AwsIam.svg',
|
||||||
|
group: ['output'],
|
||||||
|
version: 1,
|
||||||
|
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||||
|
description: 'Interacts with Amazon IAM',
|
||||||
|
defaults: { name: 'AWS IAM' },
|
||||||
|
inputs: [NodeConnectionTypes.Main],
|
||||||
|
outputs: [NodeConnectionTypes.Main],
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
name: 'aws',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
requestDefaults: {
|
||||||
|
baseURL: BASE_URL,
|
||||||
|
json: true,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
displayName: 'Resource',
|
||||||
|
name: 'resource',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
default: 'user',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'User',
|
||||||
|
value: 'user',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Group',
|
||||||
|
value: 'group',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
routing: {
|
||||||
|
send: {
|
||||||
|
preSend: [encodeBodyAsFormUrlEncoded],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...user.description,
|
||||||
|
...group.description,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
methods = {
|
||||||
|
listSearch: {
|
||||||
|
searchGroups,
|
||||||
|
searchUsers,
|
||||||
|
searchGroupsForUser,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
18
packages/nodes-base/nodes/Aws/IAM/AwsIam.svg
Normal file
18
packages/nodes-base/nodes/Aws/IAM/AwsIam.svg
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="80px" height="80px" viewBox="0 0 80 80" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 64 (93537) - https://sketch.com -->
|
||||||
|
<title>Icon-Architecture/64/Arch_AWS-Identity-and-Access-Management_64</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs>
|
||||||
|
<linearGradient x1="0%" y1="100%" x2="100%" y2="0%" id="linearGradient-1">
|
||||||
|
<stop stop-color="#BD0816" offset="0%"></stop>
|
||||||
|
<stop stop-color="#FF5252" offset="100%"></stop>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<g id="Icon-Architecture/64/Arch_AWS-Identity-and-Access-Management_64" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Icon-Architecture-BG/64/Security-Identity-Compliance" fill="url(#linearGradient-1)">
|
||||||
|
<rect id="Rectangle" x="0" y="0" width="80" height="80"></rect>
|
||||||
|
</g>
|
||||||
|
<path d="M14,59 L66,59 L66,21 L14,21 L14,59 Z M68,20 L68,60 C68,60.552 67.553,61 67,61 L13,61 C12.447,61 12,60.552 12,60 L12,20 C12,19.448 12.447,19 13,19 L67,19 C67.553,19 68,19.448 68,20 L68,20 Z M44,48 L59,48 L59,46 L44,46 L44,48 Z M57,42 L62,42 L62,40 L57,40 L57,42 Z M44,42 L52,42 L52,40 L44,40 L44,42 Z M29,46 C29,45.449 28.552,45 28,45 C27.448,45 27,45.449 27,46 C27,46.551 27.448,47 28,47 C28.552,47 29,46.551 29,46 L29,46 Z M31,46 C31,47.302 30.161,48.401 29,48.816 L29,51 L27,51 L27,48.815 C25.839,48.401 25,47.302 25,46 C25,44.346 26.346,43 28,43 C29.654,43 31,44.346 31,46 L31,46 Z M19,53.993 L36.994,54 L36.996,50 L33,50 L33,48 L36.996,48 L36.998,45 L33,45 L33,43 L36.999,43 L37,40.007 L19.006,40 L19,53.993 Z M22,38.001 L34,38.006 L34,31 C34.001,28.697 31.197,26.677 28,26.675 L27.996,26.675 C24.804,26.675 22.004,28.696 22.002,31 L22,38.001 Z M17,54.992 L17.006,39 C17.006,38.734 17.111,38.48 17.299,38.292 C17.486,38.105 17.741,38 18.006,38 L20,38.001 L20.002,31 C20.004,27.512 23.59,24.675 27.996,24.675 L28,24.675 C32.412,24.677 36.001,27.515 36,31 L36,38.007 L38,38.008 C38.553,38.008 39,38.456 39,39.008 L38.994,55 C38.994,55.266 38.889,55.52 38.701,55.708 C38.514,55.895 38.259,56 37.994,56 L18,55.992 C17.447,55.992 17,55.544 17,54.992 L17,54.992 Z M60,36 L62,36 L62,34 L60,34 L60,36 Z M44,36 L55,36 L55,34 L44,34 L44,36 Z" id="AWS-Identity-and-Access-Management_Icon_64_Squid" fill="#FFFFFF"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
158
packages/nodes-base/nodes/Aws/IAM/descriptions/common.ts
Normal file
158
packages/nodes-base/nodes/Aws/IAM/descriptions/common.ts
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { validateName } from '../helpers/utils';
|
||||||
|
|
||||||
|
export const paginationParameters: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Return All',
|
||||||
|
name: 'returnAll',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description: 'Whether to return all results or only up to a given limit',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Limit',
|
||||||
|
name: 'limit',
|
||||||
|
default: 100,
|
||||||
|
type: 'number',
|
||||||
|
validateType: 'number',
|
||||||
|
typeOptions: {
|
||||||
|
minValue: 1,
|
||||||
|
},
|
||||||
|
description: 'Max number of results to return',
|
||||||
|
displayOptions: {
|
||||||
|
hide: {
|
||||||
|
returnAll: [true],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
routing: {
|
||||||
|
send: {
|
||||||
|
property: 'MaxItems',
|
||||||
|
type: 'body',
|
||||||
|
value: '={{ $value }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const userLocator: INodeProperties = {
|
||||||
|
displayName: 'User',
|
||||||
|
name: 'user',
|
||||||
|
required: true,
|
||||||
|
type: 'resourceLocator',
|
||||||
|
default: {
|
||||||
|
mode: 'list',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'From list',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
typeOptions: {
|
||||||
|
searchListMethod: 'searchUsers',
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'By Name',
|
||||||
|
name: 'userName',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'e.g. Admins',
|
||||||
|
hint: 'Enter the user name',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: '^[\\w+=,.@-]+$',
|
||||||
|
errorMessage: 'The user name must follow the allowed pattern',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const groupLocator: INodeProperties = {
|
||||||
|
displayName: 'Group',
|
||||||
|
name: 'group',
|
||||||
|
required: true,
|
||||||
|
type: 'resourceLocator',
|
||||||
|
default: {
|
||||||
|
mode: 'list',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'From list',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
typeOptions: {
|
||||||
|
searchListMethod: 'searchGroups',
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'By Name',
|
||||||
|
name: 'groupName',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'e.g. Admins',
|
||||||
|
hint: 'Enter the group name',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: '^[\\w+=,.@-]+$',
|
||||||
|
errorMessage: 'The group name must follow the allowed pattern.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const pathParameter: INodeProperties = {
|
||||||
|
displayName: 'Path',
|
||||||
|
name: 'path',
|
||||||
|
type: 'string',
|
||||||
|
validateType: 'string',
|
||||||
|
default: '/',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const groupNameParameter: INodeProperties = {
|
||||||
|
displayName: 'Group Name',
|
||||||
|
name: 'groupName',
|
||||||
|
required: true,
|
||||||
|
type: 'string',
|
||||||
|
validateType: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
maxLength: 128,
|
||||||
|
regex: '^[+=,.@\\-_A-Za-z0-9]+$',
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
placeholder: 'e.g. GroupName',
|
||||||
|
routing: {
|
||||||
|
send: {
|
||||||
|
preSend: [validateName],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const userNameParameter: INodeProperties = {
|
||||||
|
displayName: 'User Name',
|
||||||
|
name: 'userName',
|
||||||
|
required: true,
|
||||||
|
type: 'string',
|
||||||
|
validateType: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'e.g. JohnSmith',
|
||||||
|
typeOptions: {
|
||||||
|
maxLength: 64,
|
||||||
|
regex: '^[A-Za-z0-9+=,\\.@_-]+$',
|
||||||
|
},
|
||||||
|
routing: {
|
||||||
|
send: {
|
||||||
|
preSend: [validateName],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import * as create from './create.operation';
|
||||||
|
import * as del from './delete.operation';
|
||||||
|
import * as get from './get.operation';
|
||||||
|
import * as getAll from './getAll.operation';
|
||||||
|
import * as update from './update.operation';
|
||||||
|
import { CURRENT_VERSION } from '../../helpers/constants';
|
||||||
|
import { handleError } from '../../helpers/errorHandler';
|
||||||
|
import {
|
||||||
|
deleteGroupMembers,
|
||||||
|
simplifyGetAllGroupsResponse,
|
||||||
|
simplifyGetGroupsResponse,
|
||||||
|
} from '../../helpers/utils';
|
||||||
|
|
||||||
|
export const description: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
default: 'getAll',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['group'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
value: 'create',
|
||||||
|
action: 'Create group',
|
||||||
|
description: 'Create a new group',
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
method: 'POST',
|
||||||
|
url: '',
|
||||||
|
body: {
|
||||||
|
Action: 'CreateGroup',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
GroupName: '={{ $parameter["groupName"] }}',
|
||||||
|
},
|
||||||
|
ignoreHttpStatusErrors: true,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
postReceive: [handleError],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Delete',
|
||||||
|
value: 'delete',
|
||||||
|
action: 'Delete group',
|
||||||
|
description: 'Delete an existing group',
|
||||||
|
routing: {
|
||||||
|
send: {
|
||||||
|
preSend: [deleteGroupMembers],
|
||||||
|
},
|
||||||
|
request: {
|
||||||
|
method: 'POST',
|
||||||
|
url: '',
|
||||||
|
body: {
|
||||||
|
Action: 'DeleteGroup',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
GroupName: '={{ $parameter["group"] }}',
|
||||||
|
},
|
||||||
|
ignoreHttpStatusErrors: true,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
postReceive: [handleError],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get',
|
||||||
|
value: 'get',
|
||||||
|
action: 'Get group',
|
||||||
|
description: 'Retrieve details of an existing group',
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
method: 'POST',
|
||||||
|
url: '',
|
||||||
|
body: {
|
||||||
|
Action: 'GetGroup',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
GroupName: '={{ $parameter["group"] }}',
|
||||||
|
},
|
||||||
|
ignoreHttpStatusErrors: true,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
postReceive: [handleError, simplifyGetGroupsResponse],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get Many',
|
||||||
|
value: 'getAll',
|
||||||
|
action: 'Get many groups',
|
||||||
|
description: 'Retrieve a list of groups',
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
method: 'POST',
|
||||||
|
url: '',
|
||||||
|
body: {
|
||||||
|
Action: 'ListGroups',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
},
|
||||||
|
ignoreHttpStatusErrors: true,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
postReceive: [handleError, simplifyGetAllGroupsResponse],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Update',
|
||||||
|
value: 'update',
|
||||||
|
action: 'Update group',
|
||||||
|
description: 'Update an existing group',
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
method: 'POST',
|
||||||
|
url: '',
|
||||||
|
body: {
|
||||||
|
Action: 'UpdateGroup',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
GroupName: '={{ $parameter["group"] }}',
|
||||||
|
NewGroupName: '={{ $parameter["groupName"] }}',
|
||||||
|
},
|
||||||
|
ignoreHttpStatusErrors: true,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
postReceive: [handleError],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
...create.description,
|
||||||
|
...del.description,
|
||||||
|
...get.description,
|
||||||
|
...getAll.description,
|
||||||
|
...update.description,
|
||||||
|
];
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
import { updateDisplayOptions } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { validatePath } from '../../helpers/utils';
|
||||||
|
import { groupNameParameter, pathParameter } from '../common';
|
||||||
|
|
||||||
|
const properties: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
...groupNameParameter,
|
||||||
|
description: 'The name of the new group to create',
|
||||||
|
placeholder: 'e.g. GroupName',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
...pathParameter,
|
||||||
|
placeholder: 'e.g. /division_abc/engineering/',
|
||||||
|
description: 'The path to the group, if it is not included, it defaults to a slash (/)',
|
||||||
|
routing: {
|
||||||
|
send: {
|
||||||
|
preSend: [validatePath],
|
||||||
|
property: 'Path',
|
||||||
|
type: 'query',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
placeholder: 'Add Option',
|
||||||
|
type: 'collection',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['group'],
|
||||||
|
operation: ['create'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
import { updateDisplayOptions } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { groupLocator } from '../common';
|
||||||
|
|
||||||
|
const properties: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
...groupLocator,
|
||||||
|
description: 'Select the group you want to delete',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['group'],
|
||||||
|
operation: ['delete'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
import { updateDisplayOptions } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { groupLocator } from '../common';
|
||||||
|
|
||||||
|
const properties: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
...groupLocator,
|
||||||
|
description: 'Select the group you want to retrieve',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Include Users',
|
||||||
|
name: 'includeUsers',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description: 'Whether to include a list of users in the group',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['group'],
|
||||||
|
operation: ['get'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
import { updateDisplayOptions } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { paginationParameters } from '../common';
|
||||||
|
|
||||||
|
const properties: INodeProperties[] = [
|
||||||
|
...paginationParameters,
|
||||||
|
{
|
||||||
|
displayName: 'Include Users',
|
||||||
|
name: 'includeUsers',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description: 'Whether to include a list of users in the group',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['group'],
|
||||||
|
operation: ['getAll'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
import { updateDisplayOptions } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { validatePath } from '../../helpers/utils';
|
||||||
|
import { groupLocator, groupNameParameter, pathParameter } from '../common';
|
||||||
|
|
||||||
|
const properties: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
...groupLocator,
|
||||||
|
description: 'Select the group you want to update',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...groupNameParameter,
|
||||||
|
description: 'The new name of the group',
|
||||||
|
placeholder: 'e.g. GroupName',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Option',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
...pathParameter,
|
||||||
|
placeholder: 'e.g. /division_abc/engineering/',
|
||||||
|
description: 'The new path to the group, if it is not included, it defaults to a slash (/)',
|
||||||
|
routing: {
|
||||||
|
send: {
|
||||||
|
preSend: [validatePath],
|
||||||
|
property: 'NewPath',
|
||||||
|
type: 'query',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['group'],
|
||||||
|
operation: ['update'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
2
packages/nodes-base/nodes/Aws/IAM/descriptions/index.ts
Normal file
2
packages/nodes-base/nodes/Aws/IAM/descriptions/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * as group from './group/Group.resource';
|
||||||
|
export * as user from './user/User.resource';
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import * as addToGroup from './addToGroup.operation';
|
||||||
|
import * as create from './create.operation';
|
||||||
|
import * as del from './delete.operation';
|
||||||
|
import * as get from './get.operation';
|
||||||
|
import * as getAll from './getAll.operation';
|
||||||
|
import * as removeFromGroup from './removeFromGroup.operation';
|
||||||
|
import * as update from './update.operation';
|
||||||
|
import { CURRENT_VERSION } from '../../helpers/constants';
|
||||||
|
import { handleError } from '../../helpers/errorHandler';
|
||||||
|
import { removeUserFromGroups, simplifyGetAllUsersResponse } from '../../helpers/utils';
|
||||||
|
|
||||||
|
export const description: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
default: 'getAll',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['user'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Add to Group',
|
||||||
|
value: 'addToGroup',
|
||||||
|
description: 'Add an existing user to a group',
|
||||||
|
action: 'Add user to group',
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
method: 'POST',
|
||||||
|
url: '',
|
||||||
|
body: {
|
||||||
|
Action: 'AddUserToGroup',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
UserName: '={{ $parameter["user"] }}',
|
||||||
|
GroupName: '={{ $parameter["group"] }}',
|
||||||
|
},
|
||||||
|
ignoreHttpStatusErrors: true,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
postReceive: [handleError],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
value: 'create',
|
||||||
|
description: 'Create a new user',
|
||||||
|
action: 'Create user',
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
method: 'POST',
|
||||||
|
url: '',
|
||||||
|
body: {
|
||||||
|
Action: 'CreateUser',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
UserName: '={{ $parameter["userName"] }}',
|
||||||
|
},
|
||||||
|
ignoreHttpStatusErrors: true,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
postReceive: [handleError],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Delete',
|
||||||
|
value: 'delete',
|
||||||
|
description: 'Delete a user',
|
||||||
|
action: 'Delete user',
|
||||||
|
routing: {
|
||||||
|
send: {
|
||||||
|
preSend: [removeUserFromGroups],
|
||||||
|
},
|
||||||
|
request: {
|
||||||
|
method: 'POST',
|
||||||
|
url: '',
|
||||||
|
body: {
|
||||||
|
Action: 'DeleteUser',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
UserName: '={{ $parameter["user"] }}',
|
||||||
|
},
|
||||||
|
ignoreHttpStatusErrors: true,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
postReceive: [handleError],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get',
|
||||||
|
value: 'get',
|
||||||
|
description: 'Retrieve a user',
|
||||||
|
action: 'Get user',
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
method: 'POST',
|
||||||
|
url: '',
|
||||||
|
body: {
|
||||||
|
Action: 'GetUser',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
UserName: '={{ $parameter["user"] }}',
|
||||||
|
},
|
||||||
|
ignoreHttpStatusErrors: true,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
postReceive: [
|
||||||
|
{
|
||||||
|
type: 'rootProperty',
|
||||||
|
properties: {
|
||||||
|
property: 'GetUserResponse.GetUserResult.User',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
handleError,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get Many',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'Retrieve a list of users',
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
method: 'POST',
|
||||||
|
url: '',
|
||||||
|
body: {
|
||||||
|
Action: 'ListUsers',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
},
|
||||||
|
ignoreHttpStatusErrors: true,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
postReceive: [handleError, simplifyGetAllUsersResponse],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
action: 'Get many users',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Remove From Group',
|
||||||
|
value: 'removeFromGroup',
|
||||||
|
description: 'Remove a user from a group',
|
||||||
|
action: 'Remove user from group',
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
method: 'POST',
|
||||||
|
url: '',
|
||||||
|
body: {
|
||||||
|
Action: 'RemoveUserFromGroup',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
UserName: '={{ $parameter["user"] }}',
|
||||||
|
GroupName: '={{ $parameter["group"] }}',
|
||||||
|
},
|
||||||
|
ignoreHttpStatusErrors: true,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
postReceive: [handleError],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Update',
|
||||||
|
value: 'update',
|
||||||
|
description: 'Update a user',
|
||||||
|
action: 'Update user',
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
method: 'POST',
|
||||||
|
url: '',
|
||||||
|
body: {
|
||||||
|
Action: 'UpdateUser',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
NewUserName: '={{ $parameter["userName"] }}',
|
||||||
|
UserName: '={{ $parameter["user"] }}',
|
||||||
|
},
|
||||||
|
ignoreHttpStatusErrors: true,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
postReceive: [handleError],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
...addToGroup.description,
|
||||||
|
...create.description,
|
||||||
|
...del.description,
|
||||||
|
...get.description,
|
||||||
|
...getAll.description,
|
||||||
|
...update.description,
|
||||||
|
...removeFromGroup.description,
|
||||||
|
];
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
import { updateDisplayOptions } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { groupLocator, userLocator } from '../common';
|
||||||
|
|
||||||
|
const properties: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
...userLocator,
|
||||||
|
description: 'Select the user you want to add to the group',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...groupLocator,
|
||||||
|
description: 'Select the group you want to add the user to',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['user'],
|
||||||
|
operation: ['addToGroup'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
import { updateDisplayOptions } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { preprocessTags, validatePath, validatePermissionsBoundary } from '../../helpers/utils';
|
||||||
|
import { pathParameter, userNameParameter } from '../common';
|
||||||
|
|
||||||
|
const properties: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
...userNameParameter,
|
||||||
|
description: 'The username of the new user to create',
|
||||||
|
placeholder: 'e.g. UserName',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Option',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
...pathParameter,
|
||||||
|
description: 'The path for the user name',
|
||||||
|
placeholder: 'e.g. /division_abc/subdivision_xyz/',
|
||||||
|
routing: {
|
||||||
|
send: {
|
||||||
|
preSend: [validatePath],
|
||||||
|
property: 'Path',
|
||||||
|
type: 'query',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Permissions Boundary',
|
||||||
|
name: 'permissionsBoundary',
|
||||||
|
default: '',
|
||||||
|
description:
|
||||||
|
'The ARN of the managed policy that is used to set the permissions boundary for the user',
|
||||||
|
placeholder: 'e.g. arn:aws:iam::123456789012:policy/ExampleBoundaryPolicy',
|
||||||
|
type: 'string',
|
||||||
|
validateType: 'string',
|
||||||
|
routing: {
|
||||||
|
send: {
|
||||||
|
preSend: [validatePermissionsBoundary],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Tags',
|
||||||
|
name: 'tags',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
description: 'A list of tags that you want to attach to the new user',
|
||||||
|
default: [],
|
||||||
|
placeholder: 'Add Tag',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'tags',
|
||||||
|
displayName: 'Tag',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Key',
|
||||||
|
name: 'key',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'e.g., Department',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Value',
|
||||||
|
name: 'value',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'e.g., Engineering',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
routing: {
|
||||||
|
send: {
|
||||||
|
preSend: [preprocessTags],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['user'],
|
||||||
|
operation: ['create'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
import { updateDisplayOptions } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { userLocator } from '../common';
|
||||||
|
|
||||||
|
const properties: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
...userLocator,
|
||||||
|
description: 'Select the user you want to delete',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['user'],
|
||||||
|
operation: ['delete'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
import { updateDisplayOptions } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { userLocator } from '../common';
|
||||||
|
|
||||||
|
const properties: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
...userLocator,
|
||||||
|
description: 'Select the user you want to retrieve',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['user'],
|
||||||
|
operation: ['get'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
import { updateDisplayOptions } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { validateUserPath } from '../../helpers/utils';
|
||||||
|
import { paginationParameters } from '../common';
|
||||||
|
|
||||||
|
const properties: INodeProperties[] = [
|
||||||
|
...paginationParameters,
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Path Prefix',
|
||||||
|
name: 'pathPrefix',
|
||||||
|
type: 'string',
|
||||||
|
validateType: 'string',
|
||||||
|
default: '/',
|
||||||
|
description: 'The path prefix for filtering the results',
|
||||||
|
placeholder: 'e.g. /division_abc/subdivision_xyz/',
|
||||||
|
routing: {
|
||||||
|
send: {
|
||||||
|
preSend: [validateUserPath],
|
||||||
|
property: 'PathPrefix',
|
||||||
|
value: '={{ $value }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['user'],
|
||||||
|
operation: ['getAll'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
import { updateDisplayOptions } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { groupLocator, userLocator } from '../common';
|
||||||
|
|
||||||
|
const properties: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
...userLocator,
|
||||||
|
description: 'Select the user you want to remove from the group',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...groupLocator,
|
||||||
|
description: 'Select the group you want to remove the user from',
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'From list',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
typeOptions: {
|
||||||
|
searchListMethod: 'searchGroupsForUser',
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'By Name',
|
||||||
|
name: 'groupName',
|
||||||
|
type: 'string',
|
||||||
|
hint: 'Enter the group name',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: '^[\\w+=,.@-]+$',
|
||||||
|
errorMessage: 'The group name must follow the allowed pattern',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
placeholder: 'e.g. Admins',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['user'],
|
||||||
|
operation: ['removeFromGroup'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
import { updateDisplayOptions } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { validatePath } from '../../helpers/utils';
|
||||||
|
import { pathParameter, userLocator, userNameParameter } from '../common';
|
||||||
|
|
||||||
|
const properties: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
...userLocator,
|
||||||
|
description: 'Select the user you want to update',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...userNameParameter,
|
||||||
|
description: 'The new name of the user',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Option',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
...pathParameter,
|
||||||
|
placeholder: 'e.g. /division_abc/subdivision_xyz/',
|
||||||
|
routing: {
|
||||||
|
send: {
|
||||||
|
preSend: [validatePath],
|
||||||
|
property: 'NewPath',
|
||||||
|
type: 'query',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['user'],
|
||||||
|
operation: ['update'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
15
packages/nodes-base/nodes/Aws/IAM/helpers/constants.ts
Normal file
15
packages/nodes-base/nodes/Aws/IAM/helpers/constants.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
export const CURRENT_VERSION = '2010-05-08';
|
||||||
|
export const BASE_URL = 'https://iam.amazonaws.com';
|
||||||
|
export const ERROR_DESCRIPTIONS = {
|
||||||
|
EntityAlreadyExists: {
|
||||||
|
User: 'The given user name already exists - try entering a unique name for the user.',
|
||||||
|
Group: 'The given group name already exists - try entering a unique name for the group.',
|
||||||
|
},
|
||||||
|
NoSuchEntity: {
|
||||||
|
User: 'The given user was not found - try entering a different user.',
|
||||||
|
Group: 'The given group was not found - try entering a different group.',
|
||||||
|
},
|
||||||
|
DeleteConflict: {
|
||||||
|
Default: 'Cannot delete entity, please remove users from group first.',
|
||||||
|
},
|
||||||
|
};
|
||||||
86
packages/nodes-base/nodes/Aws/IAM/helpers/errorHandler.ts
Normal file
86
packages/nodes-base/nodes/Aws/IAM/helpers/errorHandler.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import type {
|
||||||
|
JsonObject,
|
||||||
|
IDataObject,
|
||||||
|
IExecuteSingleFunctions,
|
||||||
|
IN8nHttpFullResponse,
|
||||||
|
INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { NodeApiError } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { ERROR_DESCRIPTIONS } from './constants';
|
||||||
|
import type { AwsError, ErrorMessage } from './types';
|
||||||
|
|
||||||
|
function mapErrorToResponse(errorCode: string, errorMessage: string): ErrorMessage | undefined {
|
||||||
|
const isUser = /user/i.test(errorMessage);
|
||||||
|
const isGroup = /group/i.test(errorMessage);
|
||||||
|
|
||||||
|
switch (errorCode) {
|
||||||
|
case 'EntityAlreadyExists':
|
||||||
|
if (isUser) {
|
||||||
|
return {
|
||||||
|
message: errorMessage,
|
||||||
|
description: ERROR_DESCRIPTIONS.EntityAlreadyExists.User,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (isGroup) {
|
||||||
|
return {
|
||||||
|
message: errorMessage,
|
||||||
|
description: ERROR_DESCRIPTIONS.EntityAlreadyExists.Group,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'NoSuchEntity':
|
||||||
|
if (isUser) {
|
||||||
|
return {
|
||||||
|
message: errorMessage,
|
||||||
|
description: ERROR_DESCRIPTIONS.NoSuchEntity.User,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (isGroup) {
|
||||||
|
return {
|
||||||
|
message: errorMessage,
|
||||||
|
description: ERROR_DESCRIPTIONS.NoSuchEntity.Group,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'DeleteConflict':
|
||||||
|
return {
|
||||||
|
message: errorMessage,
|
||||||
|
description: ERROR_DESCRIPTIONS.DeleteConflict.Default,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handleError(
|
||||||
|
this: IExecuteSingleFunctions,
|
||||||
|
data: INodeExecutionData[],
|
||||||
|
response: IN8nHttpFullResponse,
|
||||||
|
): Promise<INodeExecutionData[]> {
|
||||||
|
const statusCode = String(response.statusCode);
|
||||||
|
|
||||||
|
if (!statusCode.startsWith('4') && !statusCode.startsWith('5')) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseBody = response.body as IDataObject;
|
||||||
|
const error = responseBody.Error as AwsError;
|
||||||
|
|
||||||
|
if (!error) {
|
||||||
|
throw new NodeApiError(this.getNode(), response as unknown as JsonObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
const specificError = mapErrorToResponse(error.Code, error.Message);
|
||||||
|
|
||||||
|
if (specificError) {
|
||||||
|
throw new NodeApiError(this.getNode(), response as unknown as JsonObject, specificError);
|
||||||
|
} else {
|
||||||
|
throw new NodeApiError(this.getNode(), response as unknown as JsonObject, {
|
||||||
|
message: error.Code,
|
||||||
|
description: error.Message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
76
packages/nodes-base/nodes/Aws/IAM/helpers/types.ts
Normal file
76
packages/nodes-base/nodes/Aws/IAM/helpers/types.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
export type Group = {
|
||||||
|
Arn: string;
|
||||||
|
CreateDate: number;
|
||||||
|
GroupId: string;
|
||||||
|
GroupName: string;
|
||||||
|
Path?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type User = {
|
||||||
|
Arn: string;
|
||||||
|
CreateDate: number;
|
||||||
|
PasswordLastUsed?: number;
|
||||||
|
Path?: string;
|
||||||
|
PermissionsBoundary?: string;
|
||||||
|
Tags: Array<{ Key: string; Value: string }>;
|
||||||
|
UserId: string;
|
||||||
|
UserName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Tags = {
|
||||||
|
tags: Array<{ key: string; value: string }>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetUserResponseBody = {
|
||||||
|
GetUserResponse: {
|
||||||
|
GetUserResult: {
|
||||||
|
User: User;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetGroupResponseBody = {
|
||||||
|
GetGroupResponse: {
|
||||||
|
GetGroupResult: {
|
||||||
|
Group: Group;
|
||||||
|
Users?: User[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetAllUsersResponseBody = {
|
||||||
|
ListUsersResponse: {
|
||||||
|
ListUsersResult: {
|
||||||
|
Users: User[];
|
||||||
|
IsTruncated: boolean;
|
||||||
|
Marker: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetAllGroupsResponseBody = {
|
||||||
|
ListGroupsResponse: {
|
||||||
|
ListGroupsResult: {
|
||||||
|
Groups: Group[];
|
||||||
|
IsTruncated: boolean;
|
||||||
|
Marker: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AwsError = {
|
||||||
|
Code: string;
|
||||||
|
Message: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ErrorResponse = {
|
||||||
|
Error: {
|
||||||
|
Code: string;
|
||||||
|
Message: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ErrorMessage = {
|
||||||
|
message: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
323
packages/nodes-base/nodes/Aws/IAM/helpers/utils.ts
Normal file
323
packages/nodes-base/nodes/Aws/IAM/helpers/utils.ts
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
import type {
|
||||||
|
IHttpRequestOptions,
|
||||||
|
IDataObject,
|
||||||
|
IExecuteSingleFunctions,
|
||||||
|
IN8nHttpFullResponse,
|
||||||
|
INodeExecutionData,
|
||||||
|
JsonObject,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { NodeApiError, NodeOperationError } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { CURRENT_VERSION } from './constants';
|
||||||
|
import type {
|
||||||
|
GetAllGroupsResponseBody,
|
||||||
|
GetAllUsersResponseBody,
|
||||||
|
GetGroupResponseBody,
|
||||||
|
Tags,
|
||||||
|
} from './types';
|
||||||
|
import { searchGroupsForUser } from '../methods/listSearch';
|
||||||
|
import { awsApiRequest } from '../transport';
|
||||||
|
|
||||||
|
export async function encodeBodyAsFormUrlEncoded(
|
||||||
|
this: IExecuteSingleFunctions,
|
||||||
|
requestOptions: IHttpRequestOptions,
|
||||||
|
): Promise<IHttpRequestOptions> {
|
||||||
|
if (requestOptions.body) {
|
||||||
|
requestOptions.body = new URLSearchParams(
|
||||||
|
requestOptions.body as Record<string, string>,
|
||||||
|
).toString();
|
||||||
|
}
|
||||||
|
return requestOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function findUsersForGroup(
|
||||||
|
this: IExecuteSingleFunctions,
|
||||||
|
groupName: string,
|
||||||
|
): Promise<IDataObject[]> {
|
||||||
|
const options: IHttpRequestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
url: '',
|
||||||
|
body: new URLSearchParams({
|
||||||
|
Action: 'GetGroup',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
GroupName: groupName,
|
||||||
|
}).toString(),
|
||||||
|
};
|
||||||
|
const responseData = (await awsApiRequest.call(this, options)) as GetGroupResponseBody;
|
||||||
|
return responseData?.GetGroupResponse?.GetGroupResult?.Users ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function simplifyGetGroupsResponse(
|
||||||
|
this: IExecuteSingleFunctions,
|
||||||
|
_: INodeExecutionData[],
|
||||||
|
response: IN8nHttpFullResponse,
|
||||||
|
): Promise<INodeExecutionData[]> {
|
||||||
|
const includeUsers = this.getNodeParameter('includeUsers', false);
|
||||||
|
const responseBody = response.body as GetGroupResponseBody;
|
||||||
|
const groupData = responseBody.GetGroupResponse.GetGroupResult;
|
||||||
|
const group = groupData.Group;
|
||||||
|
return [
|
||||||
|
{ json: includeUsers ? { ...group, Users: groupData.Users ?? [] } : group },
|
||||||
|
] as INodeExecutionData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function simplifyGetAllGroupsResponse(
|
||||||
|
this: IExecuteSingleFunctions,
|
||||||
|
items: INodeExecutionData[],
|
||||||
|
response: IN8nHttpFullResponse,
|
||||||
|
): Promise<INodeExecutionData[]> {
|
||||||
|
const includeUsers = this.getNodeParameter('includeUsers', false);
|
||||||
|
const responseBody = response.body as GetAllGroupsResponseBody;
|
||||||
|
const groups = responseBody.ListGroupsResponse.ListGroupsResult.Groups ?? [];
|
||||||
|
|
||||||
|
if (groups.length === 0) {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!includeUsers) {
|
||||||
|
return this.helpers.returnJsonArray(groups);
|
||||||
|
}
|
||||||
|
|
||||||
|
const processedItems: IDataObject[] = [];
|
||||||
|
for (const group of groups) {
|
||||||
|
const users = await findUsersForGroup.call(this, group.GroupName);
|
||||||
|
processedItems.push({ ...group, Users: users });
|
||||||
|
}
|
||||||
|
return this.helpers.returnJsonArray(processedItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function simplifyGetAllUsersResponse(
|
||||||
|
this: IExecuteSingleFunctions,
|
||||||
|
_items: INodeExecutionData[],
|
||||||
|
response: IN8nHttpFullResponse,
|
||||||
|
): Promise<INodeExecutionData[]> {
|
||||||
|
if (!response.body) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const responseBody = response.body as GetAllUsersResponseBody;
|
||||||
|
const users = responseBody?.ListUsersResponse?.ListUsersResult?.Users ?? [];
|
||||||
|
return this.helpers.returnJsonArray(users);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteGroupMembers(
|
||||||
|
this: IExecuteSingleFunctions,
|
||||||
|
requestOptions: IHttpRequestOptions,
|
||||||
|
): Promise<IHttpRequestOptions> {
|
||||||
|
const groupName = this.getNodeParameter('group', undefined, { extractValue: true }) as string;
|
||||||
|
|
||||||
|
const users = await findUsersForGroup.call(this, groupName);
|
||||||
|
if (!users.length) {
|
||||||
|
return requestOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
users.map(async (user) => {
|
||||||
|
const userName = user.UserName as string;
|
||||||
|
if (!user.UserName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await awsApiRequest.call(this, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '',
|
||||||
|
body: {
|
||||||
|
Action: 'RemoveUserFromGroup',
|
||||||
|
GroupName: groupName,
|
||||||
|
UserName: userName,
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
},
|
||||||
|
ignoreHttpStatusErrors: true,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw new NodeApiError(this.getNode(), error as JsonObject, {
|
||||||
|
message: `Failed to remove user "${userName}" from "${groupName}"!`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return requestOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function validatePath(
|
||||||
|
this: IExecuteSingleFunctions,
|
||||||
|
requestOptions: IHttpRequestOptions,
|
||||||
|
): Promise<IHttpRequestOptions> {
|
||||||
|
const path = this.getNodeParameter('additionalFields.path') as string;
|
||||||
|
if (path.length < 1 || path.length > 512) {
|
||||||
|
throw new NodeOperationError(
|
||||||
|
this.getNode(),
|
||||||
|
'The "Path" parameter must be between 1 and 512 characters long.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const validPathRegex = /^\/[\u0021-\u007E]*\/$/;
|
||||||
|
if (!validPathRegex.test(path) && path !== '/') {
|
||||||
|
throw new NodeOperationError(
|
||||||
|
this.getNode(),
|
||||||
|
'Ensure the path is structured correctly, e.g. /division_abc/subdivision_xyz/',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function validateUserPath(
|
||||||
|
this: IExecuteSingleFunctions,
|
||||||
|
requestOptions: IHttpRequestOptions,
|
||||||
|
): Promise<IHttpRequestOptions> {
|
||||||
|
const prefix = this.getNodeParameter('additionalFields.pathPrefix') as string;
|
||||||
|
|
||||||
|
let formattedPrefix = prefix;
|
||||||
|
if (!formattedPrefix.startsWith('/')) {
|
||||||
|
formattedPrefix = '/' + formattedPrefix;
|
||||||
|
}
|
||||||
|
if (!formattedPrefix.endsWith('/') && formattedPrefix !== '/') {
|
||||||
|
formattedPrefix = formattedPrefix + '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestOptions.body && typeof requestOptions.body === 'object') {
|
||||||
|
Object.assign(requestOptions.body, { PathPrefix: formattedPrefix });
|
||||||
|
}
|
||||||
|
|
||||||
|
const options: IHttpRequestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
url: '',
|
||||||
|
body: {
|
||||||
|
Action: 'ListUsers',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const responseData = (await awsApiRequest.call(this, options)) as GetAllUsersResponseBody;
|
||||||
|
|
||||||
|
const users = responseData.ListUsersResponse.ListUsersResult.Users;
|
||||||
|
if (!users || users.length === 0) {
|
||||||
|
throw new NodeOperationError(
|
||||||
|
this.getNode(),
|
||||||
|
'No users found. Please adjust the "Path" parameter and try again.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const userPaths = users.map((user) => user.Path).filter(Boolean);
|
||||||
|
const isPathValid = userPaths.some((path) => path?.startsWith(formattedPrefix));
|
||||||
|
if (!isPathValid) {
|
||||||
|
throw new NodeOperationError(
|
||||||
|
this.getNode(),
|
||||||
|
`The "${formattedPrefix}" path was not found in your users. Try entering a different path.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return requestOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function validateName(
|
||||||
|
this: IExecuteSingleFunctions,
|
||||||
|
requestOptions: IHttpRequestOptions,
|
||||||
|
): Promise<IHttpRequestOptions> {
|
||||||
|
const resource = this.getNodeParameter('resource') as string;
|
||||||
|
const nameParam = resource === 'user' ? 'userName' : 'groupName';
|
||||||
|
const name = this.getNodeParameter(nameParam) as string;
|
||||||
|
|
||||||
|
const maxLength = resource === 'user' ? 64 : 128;
|
||||||
|
const capitalizedResource = resource.replace(/^./, (c) => c.toUpperCase());
|
||||||
|
const validNamePattern = /^[a-zA-Z0-9-_]+$/;
|
||||||
|
|
||||||
|
const isInvalid = !validNamePattern.test(name) || name.length > maxLength;
|
||||||
|
|
||||||
|
if (/\s/.test(name)) {
|
||||||
|
throw new NodeOperationError(
|
||||||
|
this.getNode(),
|
||||||
|
`${capitalizedResource} name should not contain spaces.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInvalid) {
|
||||||
|
throw new NodeOperationError(
|
||||||
|
this.getNode(),
|
||||||
|
`${capitalizedResource} name can have up to ${maxLength} characters. Valid characters: letters, numbers, hyphens (-), and underscores (_).`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function validatePermissionsBoundary(
|
||||||
|
this: IExecuteSingleFunctions,
|
||||||
|
requestOptions: IHttpRequestOptions,
|
||||||
|
): Promise<IHttpRequestOptions> {
|
||||||
|
const permissionsBoundary = this.getNodeParameter(
|
||||||
|
'additionalFields.permissionsBoundary',
|
||||||
|
) as string;
|
||||||
|
|
||||||
|
if (permissionsBoundary) {
|
||||||
|
const arnPattern = /^arn:aws:iam::\d{12}:policy\/[\w\-+\/=._]+$/;
|
||||||
|
|
||||||
|
if (!arnPattern.test(permissionsBoundary)) {
|
||||||
|
throw new NodeOperationError(
|
||||||
|
this.getNode(),
|
||||||
|
'Permissions boundaries must be provided in ARN format (e.g. arn:aws:iam::123456789012:policy/ExampleBoundaryPolicy). These can be found at the top of the permissions boundary detail page in the IAM dashboard.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestOptions.body) {
|
||||||
|
Object.assign(requestOptions.body, { PermissionsBoundary: permissionsBoundary });
|
||||||
|
} else {
|
||||||
|
requestOptions.body = {
|
||||||
|
PermissionsBoundary: permissionsBoundary,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return requestOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function preprocessTags(
|
||||||
|
this: IExecuteSingleFunctions,
|
||||||
|
requestOptions: IHttpRequestOptions,
|
||||||
|
): Promise<IHttpRequestOptions> {
|
||||||
|
const tagsData = this.getNodeParameter('additionalFields.tags') as Tags;
|
||||||
|
const tags = tagsData?.tags || [];
|
||||||
|
|
||||||
|
let bodyObj: Record<string, string> = {};
|
||||||
|
if (typeof requestOptions.body === 'string') {
|
||||||
|
const params = new URLSearchParams(requestOptions.body);
|
||||||
|
bodyObj = Object.fromEntries(params.entries());
|
||||||
|
}
|
||||||
|
|
||||||
|
tags.forEach((tag, index) => {
|
||||||
|
if (!tag.key || !tag.value) {
|
||||||
|
throw new NodeOperationError(
|
||||||
|
this.getNode(),
|
||||||
|
`Tag at position ${index + 1} is missing '${!tag.key ? 'Key' : 'Value'}'. Both 'Key' and 'Value' are required.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
bodyObj[`Tags.member.${index + 1}.Key`] = tag.key;
|
||||||
|
bodyObj[`Tags.member.${index + 1}.Value`] = tag.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
requestOptions.body = new URLSearchParams(bodyObj).toString();
|
||||||
|
|
||||||
|
return requestOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeUserFromGroups(
|
||||||
|
this: IExecuteSingleFunctions,
|
||||||
|
requestOptions: IHttpRequestOptions,
|
||||||
|
): Promise<IHttpRequestOptions> {
|
||||||
|
const userName = this.getNodeParameter('user', undefined, { extractValue: true });
|
||||||
|
const userGroups = await searchGroupsForUser.call(this);
|
||||||
|
|
||||||
|
for (const group of userGroups.results) {
|
||||||
|
await awsApiRequest.call(this, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '',
|
||||||
|
body: {
|
||||||
|
Action: 'RemoveUserFromGroup',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
GroupName: group.value,
|
||||||
|
UserName: userName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestOptions;
|
||||||
|
}
|
||||||
1
packages/nodes-base/nodes/Aws/IAM/methods/index.ts
Normal file
1
packages/nodes-base/nodes/Aws/IAM/methods/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * as listSearch from './listSearch';
|
||||||
159
packages/nodes-base/nodes/Aws/IAM/methods/listSearch.ts
Normal file
159
packages/nodes-base/nodes/Aws/IAM/methods/listSearch.ts
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
import type {
|
||||||
|
IDataObject,
|
||||||
|
IExecuteSingleFunctions,
|
||||||
|
IHttpRequestOptions,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
INodeListSearchItems,
|
||||||
|
INodeListSearchResult,
|
||||||
|
JsonObject,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { NodeApiError } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { CURRENT_VERSION } from '../helpers/constants';
|
||||||
|
import type {
|
||||||
|
GetAllGroupsResponseBody,
|
||||||
|
GetAllUsersResponseBody,
|
||||||
|
GetGroupResponseBody,
|
||||||
|
} from '../helpers/types';
|
||||||
|
import { awsApiRequest } from '../transport';
|
||||||
|
|
||||||
|
function formatSearchResults(
|
||||||
|
items: IDataObject[],
|
||||||
|
propertyName: string,
|
||||||
|
filter?: string,
|
||||||
|
): INodeListSearchItems[] {
|
||||||
|
return items
|
||||||
|
.map((item) => ({
|
||||||
|
name: String(item[propertyName] ?? ''),
|
||||||
|
value: String(item[propertyName] ?? ''),
|
||||||
|
}))
|
||||||
|
.filter(({ name }) => !filter || name.includes(filter))
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function searchUsers(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
filter?: string,
|
||||||
|
paginationToken?: string,
|
||||||
|
): Promise<INodeListSearchResult> {
|
||||||
|
const options: IHttpRequestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
url: '',
|
||||||
|
body: {
|
||||||
|
Action: 'ListUsers',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
...(paginationToken ? { Marker: paginationToken } : {}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const responseData = (await awsApiRequest.call(this, options)) as GetAllUsersResponseBody;
|
||||||
|
|
||||||
|
const users = responseData.ListUsersResponse.ListUsersResult.Users || [];
|
||||||
|
const nextMarker = responseData.ListUsersResponse.ListUsersResult.IsTruncated
|
||||||
|
? responseData.ListUsersResponse.ListUsersResult.Marker
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
results: formatSearchResults(users, 'UserName', filter),
|
||||||
|
paginationToken: nextMarker,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function searchGroups(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
filter?: string,
|
||||||
|
paginationToken?: string,
|
||||||
|
): Promise<INodeListSearchResult> {
|
||||||
|
const options: IHttpRequestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
url: '',
|
||||||
|
body: {
|
||||||
|
Action: 'ListGroups',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
...(paginationToken ? { Marker: paginationToken } : {}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const responseData = (await awsApiRequest.call(this, options)) as GetAllGroupsResponseBody;
|
||||||
|
|
||||||
|
const groups = responseData.ListGroupsResponse.ListGroupsResult.Groups || [];
|
||||||
|
const nextMarker = responseData.ListGroupsResponse.ListGroupsResult.IsTruncated
|
||||||
|
? responseData.ListGroupsResponse.ListGroupsResult.Marker
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
results: formatSearchResults(groups, 'GroupName', filter),
|
||||||
|
paginationToken: nextMarker,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function searchGroupsForUser(
|
||||||
|
this: ILoadOptionsFunctions | IExecuteSingleFunctions,
|
||||||
|
filter?: string,
|
||||||
|
): Promise<INodeListSearchResult> {
|
||||||
|
const userName = this.getNodeParameter('user', undefined, { extractValue: true });
|
||||||
|
let allGroups: IDataObject[] = [];
|
||||||
|
let nextMarkerGroups: string | undefined;
|
||||||
|
do {
|
||||||
|
const options: IHttpRequestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
url: '',
|
||||||
|
body: {
|
||||||
|
Action: 'ListGroups',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
...(nextMarkerGroups ? { Marker: nextMarkerGroups } : {}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const groupsData = (await awsApiRequest.call(this, options)) as GetAllGroupsResponseBody;
|
||||||
|
|
||||||
|
const groups = groupsData.ListGroupsResponse?.ListGroupsResult?.Groups || [];
|
||||||
|
nextMarkerGroups = groupsData.ListGroupsResponse?.ListGroupsResult?.IsTruncated
|
||||||
|
? groupsData.ListGroupsResponse?.ListGroupsResult?.Marker
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
allGroups = [...allGroups, ...groups];
|
||||||
|
} while (nextMarkerGroups);
|
||||||
|
|
||||||
|
if (allGroups.length === 0) {
|
||||||
|
return { results: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupCheckPromises = allGroups.map(async (group) => {
|
||||||
|
const groupName = group.GroupName as string;
|
||||||
|
if (!groupName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options: IHttpRequestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
url: '',
|
||||||
|
body: {
|
||||||
|
Action: 'GetGroup',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
GroupName: groupName,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const getGroupResponse = (await awsApiRequest.call(this, options)) as GetGroupResponseBody;
|
||||||
|
const groupResult = getGroupResponse?.GetGroupResponse?.GetGroupResult;
|
||||||
|
const userExists = groupResult?.Users?.some((user) => user.UserName === userName);
|
||||||
|
|
||||||
|
if (userExists) {
|
||||||
|
return { UserName: userName, GroupName: groupName };
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new NodeApiError(this.getNode(), error as JsonObject, {
|
||||||
|
message: `Failed to get group ${groupName}: ${error?.message ?? 'Unknown error'}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const validUserGroups = (await Promise.all(groupCheckPromises)).filter(Boolean) as IDataObject[];
|
||||||
|
|
||||||
|
return {
|
||||||
|
results: formatSearchResults(validUserGroups, 'GroupName', filter),
|
||||||
|
};
|
||||||
|
}
|
||||||
45
packages/nodes-base/nodes/Aws/IAM/test/group/create.test.ts
Normal file
45
packages/nodes-base/nodes/Aws/IAM/test/group/create.test.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||||
|
import nock from 'nock';
|
||||||
|
|
||||||
|
import { BASE_URL, CURRENT_VERSION } from '../../helpers/constants';
|
||||||
|
|
||||||
|
describe('AWS IAM - Create Group', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
nock.cleanAll();
|
||||||
|
nock(BASE_URL)
|
||||||
|
.persist()
|
||||||
|
.defaultReplyHeaders({ 'Content-Type': 'application/x-amz-json-1.1' })
|
||||||
|
.post('/', {
|
||||||
|
Action: 'CreateGroup',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
GroupName: 'NewGroupTest',
|
||||||
|
})
|
||||||
|
.reply(200, {
|
||||||
|
CreateGroupResponse: {
|
||||||
|
CreateGroupResult: {
|
||||||
|
Group: {
|
||||||
|
GroupName: 'NewGroupTest',
|
||||||
|
Arn: 'arn:aws:iam::130450532146:group/NewGroupTest',
|
||||||
|
GroupId: 'AGPAR4X3VE4ZI7H42C2XW',
|
||||||
|
Path: '/',
|
||||||
|
CreateDate: 1743409792,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ResponseMetadata: {
|
||||||
|
RequestId: '50bf4fdb-34b9-4c99-8da8-358d50783c8d',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new NodeTestHarness().setupTests({
|
||||||
|
workflowFiles: ['create.workflow.json'],
|
||||||
|
credentials: {
|
||||||
|
aws: {
|
||||||
|
region: 'eu-central-1',
|
||||||
|
accessKeyId: 'test',
|
||||||
|
secretAccessKey: 'test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [-40, -120],
|
||||||
|
"id": "7da2ce49-9a9d-4240-b082-ff1b12d101b1",
|
||||||
|
"name": "When clicking ‘Test workflow’"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"resource": "group",
|
||||||
|
"operation": "create",
|
||||||
|
"groupName": "NewGroupTest",
|
||||||
|
"additionalFields": {},
|
||||||
|
"requestOptions": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.awsIam",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [160, -120],
|
||||||
|
"id": "23fd4c79-516d-49dc-81c8-e68052a294d1",
|
||||||
|
"name": "createGroup",
|
||||||
|
"credentials": {
|
||||||
|
"aws": {
|
||||||
|
"id": "exampleId",
|
||||||
|
"name": "AWS US EAST"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {
|
||||||
|
"createGroup": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"CreateGroupResponse": {
|
||||||
|
"CreateGroupResult": {
|
||||||
|
"Group": {
|
||||||
|
"Arn": "arn:aws:iam::130450532146:group/NewGroupTest",
|
||||||
|
"CreateDate": 1743409792,
|
||||||
|
"GroupId": "AGPAR4X3VE4ZI7H42C2XW",
|
||||||
|
"GroupName": "NewGroupTest",
|
||||||
|
"Path": "/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ResponseMetadata": {
|
||||||
|
"RequestId": "50bf4fdb-34b9-4c99-8da8-358d50783c8d"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"When clicking ‘Test workflow’": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "createGroup",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"versionId": "da88fb89-6234-4eb3-b6fb-31a7a299c7ec",
|
||||||
|
"meta": {
|
||||||
|
"templateCredsSetupCompleted": true,
|
||||||
|
"instanceId": "26f38eb23ad84214537831cfef4032299ffac994ff18d5cb72e82d31ac4ceac4"
|
||||||
|
},
|
||||||
|
"id": "0qqycx08fTBpfbC9",
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
61
packages/nodes-base/nodes/Aws/IAM/test/group/delete.test.ts
Normal file
61
packages/nodes-base/nodes/Aws/IAM/test/group/delete.test.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||||
|
import nock from 'nock';
|
||||||
|
|
||||||
|
import { BASE_URL, CURRENT_VERSION } from '../../helpers/constants';
|
||||||
|
|
||||||
|
describe('AWS IAM - Delete Group', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
nock.cleanAll();
|
||||||
|
nock(BASE_URL)
|
||||||
|
.persist()
|
||||||
|
.defaultReplyHeaders({ 'Content-Type': 'application/x-amz-json-1.1' })
|
||||||
|
.post('/', {
|
||||||
|
Action: 'GetGroup',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
GroupName: 'GroupForTest1',
|
||||||
|
})
|
||||||
|
.reply(200, {
|
||||||
|
GetGroupResponse: {
|
||||||
|
GetGroupResult: {
|
||||||
|
Users: [{ UserName: 'User1' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
nock(BASE_URL)
|
||||||
|
.persist()
|
||||||
|
.defaultReplyHeaders({ 'Content-Type': 'application/x-amz-json-1.1' })
|
||||||
|
.post('/', {
|
||||||
|
Action: 'RemoveUserFromGroup',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
GroupName: 'GroupForTest1',
|
||||||
|
UserName: 'User1',
|
||||||
|
})
|
||||||
|
.reply(200, {});
|
||||||
|
nock(BASE_URL)
|
||||||
|
.persist()
|
||||||
|
.defaultReplyHeaders({ 'Content-Type': 'application/x-amz-json-1.1' })
|
||||||
|
.post('/', {
|
||||||
|
Action: 'DeleteGroup',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
GroupName: 'GroupForTest1',
|
||||||
|
})
|
||||||
|
.reply(200, {
|
||||||
|
DeleteGroupResponse: {
|
||||||
|
ResponseMetadata: {
|
||||||
|
RequestId: 'b9cc2642-db2c-4935-aaaf-eacf10e4f00a',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new NodeTestHarness().setupTests({
|
||||||
|
workflowFiles: ['delete.workflow.json'],
|
||||||
|
credentials: {
|
||||||
|
aws: {
|
||||||
|
region: 'eu-central-1',
|
||||||
|
accessKeyId: 'test',
|
||||||
|
secretAccessKey: 'test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [200, 120],
|
||||||
|
"id": "b4205abf-7102-4e53-8aed-7bd047acfaf4",
|
||||||
|
"name": "When clicking ‘Test workflow’"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"resource": "group",
|
||||||
|
"operation": "delete",
|
||||||
|
"group": {
|
||||||
|
"__rl": true,
|
||||||
|
"value": "GroupForTest1",
|
||||||
|
"mode": "list",
|
||||||
|
"cachedResultName": "GroupForTest1"
|
||||||
|
},
|
||||||
|
"requestOptions": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.awsIam",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [380, 120],
|
||||||
|
"id": "bba99f6d-ed9c-4603-95f0-4ecce6e6f976",
|
||||||
|
"name": "AWS IAM13",
|
||||||
|
"credentials": {
|
||||||
|
"aws": {
|
||||||
|
"id": "exampleId",
|
||||||
|
"name": "AWS US EAST"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"connections": {
|
||||||
|
"When clicking ‘Test workflow’": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "AWS IAM13",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pinData": {
|
||||||
|
"AWS IAM13": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"DeleteGroupResponse": {
|
||||||
|
"ResponseMetadata": {
|
||||||
|
"RequestId": "b9cc2642-db2c-4935-aaaf-eacf10e4f00a"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
54
packages/nodes-base/nodes/Aws/IAM/test/group/get.test.ts
Normal file
54
packages/nodes-base/nodes/Aws/IAM/test/group/get.test.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||||
|
import nock from 'nock';
|
||||||
|
|
||||||
|
import { BASE_URL, CURRENT_VERSION } from '../../helpers/constants';
|
||||||
|
|
||||||
|
describe('AWS IAM - Get Group', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
nock.cleanAll();
|
||||||
|
nock(BASE_URL)
|
||||||
|
.persist()
|
||||||
|
.defaultReplyHeaders({ 'Content-Type': 'application/x-amz-json-1.1' })
|
||||||
|
.post('/', {
|
||||||
|
Action: 'GetGroup',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
GroupName: 'GroupNameUpdated2',
|
||||||
|
})
|
||||||
|
.reply(200, {
|
||||||
|
GetGroupResponse: {
|
||||||
|
GetGroupResult: {
|
||||||
|
Group: {
|
||||||
|
Arn: 'arn:aws:iam::130450532146:group/New/Path/GroupNameUpdated2',
|
||||||
|
CreateDate: 1739193696,
|
||||||
|
GroupId: 'AGPAR4X3VE4ZKHNKBQHBZ',
|
||||||
|
GroupName: 'GroupNameUpdated2',
|
||||||
|
Path: '/New/Path/',
|
||||||
|
},
|
||||||
|
Users: [
|
||||||
|
{
|
||||||
|
Arn: 'arn:aws:iam::130450532146:user/rhis/path/Jonas',
|
||||||
|
CreateDate: 1739198295,
|
||||||
|
PasswordLastUsed: null,
|
||||||
|
PermissionsBoundary: null,
|
||||||
|
Tags: null,
|
||||||
|
Path: '/rhis/path/',
|
||||||
|
UserId: 'AIDAR4X3VE4ZDJJFKI6OU',
|
||||||
|
UserName: 'Jonas',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new NodeTestHarness().setupTests({
|
||||||
|
workflowFiles: ['get.workflow.json'],
|
||||||
|
credentials: {
|
||||||
|
aws: {
|
||||||
|
region: 'eu-central-1',
|
||||||
|
accessKeyId: 'test',
|
||||||
|
secretAccessKey: 'test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [-80, -100],
|
||||||
|
"id": "7da2ce49-9a9d-4240-b082-ff1b12d101b1",
|
||||||
|
"name": "When clicking ‘Test workflow’"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"resource": "group",
|
||||||
|
"operation": "get",
|
||||||
|
"group": {
|
||||||
|
"__rl": true,
|
||||||
|
"value": "GroupNameUpdated2",
|
||||||
|
"mode": "list",
|
||||||
|
"cachedResultName": "GroupNameUpdated2"
|
||||||
|
},
|
||||||
|
"includeUsers": true,
|
||||||
|
"requestOptions": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.awsIam",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [160, -100],
|
||||||
|
"id": "7990c8f3-738e-4150-855e-a9c0a965b75d",
|
||||||
|
"name": "getGroup",
|
||||||
|
"credentials": {
|
||||||
|
"aws": {
|
||||||
|
"id": "exampleId",
|
||||||
|
"name": "AWS US EAST"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {
|
||||||
|
"getGroup": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"Arn": "arn:aws:iam::130450532146:group/New/Path/GroupNameUpdated2",
|
||||||
|
"CreateDate": 1739193696,
|
||||||
|
"GroupId": "AGPAR4X3VE4ZKHNKBQHBZ",
|
||||||
|
"GroupName": "GroupNameUpdated2",
|
||||||
|
"Path": "/New/Path/",
|
||||||
|
"Users": [
|
||||||
|
{
|
||||||
|
"Arn": "arn:aws:iam::130450532146:user/rhis/path/Jonas",
|
||||||
|
"CreateDate": 1739198295,
|
||||||
|
"PasswordLastUsed": null,
|
||||||
|
"Path": "/rhis/path/",
|
||||||
|
"PermissionsBoundary": null,
|
||||||
|
"Tags": null,
|
||||||
|
"UserId": "AIDAR4X3VE4ZDJJFKI6OU",
|
||||||
|
"UserName": "Jonas"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"When clicking ‘Test workflow’": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "getGroup",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"versionId": "47e9f15a-50b7-4f12-ac5b-c7b24bb9a400",
|
||||||
|
"meta": {
|
||||||
|
"templateCredsSetupCompleted": true,
|
||||||
|
"instanceId": "26f38eb23ad84214537831cfef4032299ffac994ff18d5cb72e82d31ac4ceac4"
|
||||||
|
},
|
||||||
|
"id": "0qqycx08fTBpfbC9",
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
58
packages/nodes-base/nodes/Aws/IAM/test/group/getAll.test.ts
Normal file
58
packages/nodes-base/nodes/Aws/IAM/test/group/getAll.test.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||||
|
import nock from 'nock';
|
||||||
|
|
||||||
|
import { BASE_URL, CURRENT_VERSION } from '../../helpers/constants';
|
||||||
|
|
||||||
|
describe('AWS IAM - Get All Groups', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
nock.cleanAll();
|
||||||
|
nock(BASE_URL)
|
||||||
|
.persist()
|
||||||
|
.defaultReplyHeaders({ 'Content-Type': 'application/x-amz-json-1.1' })
|
||||||
|
.post('/', {
|
||||||
|
Action: 'ListGroups',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
MaxItems: 100,
|
||||||
|
})
|
||||||
|
.reply(200, {
|
||||||
|
ListGroupsResponse: {
|
||||||
|
ListGroupsResult: {
|
||||||
|
Groups: [
|
||||||
|
{
|
||||||
|
Arn: 'arn:aws:iam::130450532146:group/test/Admin7',
|
||||||
|
CreateDate: 1733436631,
|
||||||
|
GroupId: 'AGPAR4X3VE4ZAFFY5EDUJ',
|
||||||
|
GroupName: 'Admin7',
|
||||||
|
Path: '/test/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Arn: 'arn:aws:iam::130450532146:group/cognito',
|
||||||
|
CreateDate: 1730804196,
|
||||||
|
GroupId: 'AGPAR4X3VE4ZMVEFLBSRB',
|
||||||
|
GroupName: 'cognito',
|
||||||
|
Path: '/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Arn: 'arn:aws:iam::130450532146:group/GroupCreatedAfter',
|
||||||
|
CreateDate: 1741589366,
|
||||||
|
GroupId: 'AGPAR4X3VE4ZF5VE6UF2U',
|
||||||
|
GroupName: 'GroupCreatedAfter',
|
||||||
|
Path: '/',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new NodeTestHarness().setupTests({
|
||||||
|
workflowFiles: ['getAll.workflow.json'],
|
||||||
|
credentials: {
|
||||||
|
aws: {
|
||||||
|
region: 'eu-central-1',
|
||||||
|
accessKeyId: 'test',
|
||||||
|
secretAccessKey: 'test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [-40, -220],
|
||||||
|
"id": "7da2ce49-9a9d-4240-b082-ff1b12d101b1",
|
||||||
|
"name": "When clicking ‘Test workflow’"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"resource": "group",
|
||||||
|
"includeUsers": false,
|
||||||
|
"requestOptions": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.awsIam",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [160, -220],
|
||||||
|
"id": "23fd4c79-516d-49dc-81c8-e68052a294d1",
|
||||||
|
"name": "getAllGroups",
|
||||||
|
"credentials": {
|
||||||
|
"aws": {
|
||||||
|
"id": "exampleId",
|
||||||
|
"name": "AWS US EAST"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {
|
||||||
|
"getAllGroups": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"Arn": "arn:aws:iam::130450532146:group/test/Admin7",
|
||||||
|
"CreateDate": 1733436631,
|
||||||
|
"GroupId": "AGPAR4X3VE4ZAFFY5EDUJ",
|
||||||
|
"GroupName": "Admin7",
|
||||||
|
"Path": "/test/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"Arn": "arn:aws:iam::130450532146:group/cognito",
|
||||||
|
"CreateDate": 1730804196,
|
||||||
|
"GroupId": "AGPAR4X3VE4ZMVEFLBSRB",
|
||||||
|
"GroupName": "cognito",
|
||||||
|
"Path": "/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"Arn": "arn:aws:iam::130450532146:group/GroupCreatedAfter",
|
||||||
|
"CreateDate": 1741589366,
|
||||||
|
"GroupId": "AGPAR4X3VE4ZF5VE6UF2U",
|
||||||
|
"GroupName": "GroupCreatedAfter",
|
||||||
|
"Path": "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"When clicking ‘Test workflow’": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "getAllGroups",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"versionId": "51beb4c0-4163-4c57-a715-6eb45f1ffb9b",
|
||||||
|
"meta": {
|
||||||
|
"templateCredsSetupCompleted": true,
|
||||||
|
"instanceId": "26f38eb23ad84214537831cfef4032299ffac994ff18d5cb72e82d31ac4ceac4"
|
||||||
|
},
|
||||||
|
"id": "0qqycx08fTBpfbC9",
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
36
packages/nodes-base/nodes/Aws/IAM/test/group/update.test.ts
Normal file
36
packages/nodes-base/nodes/Aws/IAM/test/group/update.test.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||||
|
import nock from 'nock';
|
||||||
|
|
||||||
|
import { BASE_URL, CURRENT_VERSION } from '../../helpers/constants';
|
||||||
|
|
||||||
|
describe('AWS IAM - Update Group', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
nock(BASE_URL)
|
||||||
|
.persist()
|
||||||
|
.defaultReplyHeaders({ 'Content-Type': 'application/x-amz-json-1.1' })
|
||||||
|
.post('/', {
|
||||||
|
Action: 'UpdateGroup',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
GroupName: 'GroupNameUpdated',
|
||||||
|
NewGroupName: 'GroupNameUpdated2',
|
||||||
|
})
|
||||||
|
.reply(200, {
|
||||||
|
UpdateGroupResponse: {
|
||||||
|
ResponseMetadata: {
|
||||||
|
RequestId: '16ada465-a981-44ab-841f-3ca3247f7405',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new NodeTestHarness().setupTests({
|
||||||
|
workflowFiles: ['update.workflow.json'],
|
||||||
|
credentials: {
|
||||||
|
aws: {
|
||||||
|
region: 'eu-central-1',
|
||||||
|
accessKeyId: 'test',
|
||||||
|
secretAccessKey: 'test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [-40, -120],
|
||||||
|
"id": "7da2ce49-9a9d-4240-b082-ff1b12d101b1",
|
||||||
|
"name": "When clicking ‘Test workflow’"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"resource": "group",
|
||||||
|
"operation": "update",
|
||||||
|
"group": {
|
||||||
|
"__rl": true,
|
||||||
|
"value": "GroupNameUpdated",
|
||||||
|
"mode": "list",
|
||||||
|
"cachedResultName": "GroupNameUpdated"
|
||||||
|
},
|
||||||
|
"groupName": "GroupNameUpdated2",
|
||||||
|
"requestOptions": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.awsIam",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [140, -120],
|
||||||
|
"id": "6299cf80-7aea-4cf6-8604-368a42290da7",
|
||||||
|
"name": "updateGroup",
|
||||||
|
"credentials": {
|
||||||
|
"aws": {
|
||||||
|
"id": "exampleId",
|
||||||
|
"name": "AWS US EAST"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {
|
||||||
|
"updateGroup": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"UpdateGroupResponse": {
|
||||||
|
"ResponseMetadata": {
|
||||||
|
"RequestId": "16ada465-a981-44ab-841f-3ca3247f7405"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"When clicking ‘Test workflow’": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "updateGroup",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"versionId": "bce6ece0-c45a-4389-9e39-6edea3d2e0da",
|
||||||
|
"meta": {
|
||||||
|
"templateCredsSetupCompleted": true,
|
||||||
|
"instanceId": "26f38eb23ad84214537831cfef4032299ffac994ff18d5cb72e82d31ac4ceac4"
|
||||||
|
},
|
||||||
|
"id": "0qqycx08fTBpfbC9",
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
import { NodeApiError } from 'n8n-workflow';
|
||||||
|
import type { INodeExecutionData, IN8nHttpFullResponse, JsonObject } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { ERROR_DESCRIPTIONS } from '../../helpers/constants';
|
||||||
|
import { handleError } from '../../helpers/errorHandler';
|
||||||
|
|
||||||
|
const mockExecuteSingleFunctions = {
|
||||||
|
getNode: jest.fn(() => ({ name: 'MockNode' })),
|
||||||
|
getNodeParameter: jest.fn(),
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
describe('handleError', () => {
|
||||||
|
let response: IN8nHttpFullResponse;
|
||||||
|
let data: INodeExecutionData[];
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
data = [{}] as INodeExecutionData[];
|
||||||
|
response = { statusCode: 200, body: {} } as IN8nHttpFullResponse;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return data when no error occurs', async () => {
|
||||||
|
const result = await handleError.call(mockExecuteSingleFunctions, data, response);
|
||||||
|
expect(result).toBe(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should throw NodeApiError for EntityAlreadyExists with user conflict', async () => {
|
||||||
|
mockExecuteSingleFunctions.getNodeParameter = jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValueOnce('user')
|
||||||
|
.mockReturnValueOnce('existingUserName');
|
||||||
|
|
||||||
|
response.statusCode = 400;
|
||||||
|
response.body = {
|
||||||
|
Error: { Code: 'EntityAlreadyExists', Message: 'User "existingUserName" already exists' },
|
||||||
|
} as JsonObject;
|
||||||
|
|
||||||
|
await expect(handleError.call(mockExecuteSingleFunctions, data, response)).rejects.toThrow(
|
||||||
|
new NodeApiError(mockExecuteSingleFunctions.getNode(), response.body as JsonObject, {
|
||||||
|
message: 'User "existingUserName" already exists',
|
||||||
|
description: ERROR_DESCRIPTIONS.EntityAlreadyExists.User,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should throw NodeApiError for NoSuchEntity with user not found', async () => {
|
||||||
|
mockExecuteSingleFunctions.getNodeParameter
|
||||||
|
.mockReturnValueOnce('user')
|
||||||
|
.mockReturnValueOnce('nonExistentUser');
|
||||||
|
|
||||||
|
response.statusCode = 404;
|
||||||
|
response.body = {
|
||||||
|
Error: { Code: 'NoSuchEntity', Message: 'User "nonExistentUser" does not exist' },
|
||||||
|
} as JsonObject;
|
||||||
|
|
||||||
|
await expect(handleError.call(mockExecuteSingleFunctions, data, response)).rejects.toThrowError(
|
||||||
|
new NodeApiError(mockExecuteSingleFunctions.getNode(), response.body as JsonObject, {
|
||||||
|
message: 'User "nonExistentUser" does not exist',
|
||||||
|
description: ERROR_DESCRIPTIONS.NoSuchEntity.User,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should throw generic error if no specific mapping exists', async () => {
|
||||||
|
mockExecuteSingleFunctions.getNodeParameter.mockReturnValue('container');
|
||||||
|
|
||||||
|
response.statusCode = 400;
|
||||||
|
response.body = { Error: { Code: 'BadRequest', Message: 'Invalid request' } } as JsonObject;
|
||||||
|
|
||||||
|
await expect(handleError.call(mockExecuteSingleFunctions, data, response)).rejects.toThrow(
|
||||||
|
new NodeApiError(mockExecuteSingleFunctions.getNode(), response.body as JsonObject, {
|
||||||
|
message: 'BadRequest',
|
||||||
|
description: 'Invalid request',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should throw NodeApiError for EntityAlreadyExists with group conflict', async () => {
|
||||||
|
mockExecuteSingleFunctions.getNodeParameter
|
||||||
|
.mockReturnValueOnce('group')
|
||||||
|
.mockReturnValue('existingGroupName');
|
||||||
|
|
||||||
|
response.statusCode = 400;
|
||||||
|
response.body = {
|
||||||
|
Error: { Code: 'EntityAlreadyExists', Message: 'Group "existingGroupName" already exists' },
|
||||||
|
} as JsonObject;
|
||||||
|
|
||||||
|
await expect(handleError.call(mockExecuteSingleFunctions, data, response)).rejects.toThrow(
|
||||||
|
new NodeApiError(mockExecuteSingleFunctions.getNode(), response.body as JsonObject, {
|
||||||
|
message: 'Group "existingGroupName" already exists',
|
||||||
|
description: ERROR_DESCRIPTIONS.EntityAlreadyExists.Group,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should throw NodeApiError for NoSuchEntity with group not found', async () => {
|
||||||
|
mockExecuteSingleFunctions.getNodeParameter
|
||||||
|
.mockReturnValueOnce('group')
|
||||||
|
.mockReturnValue('nonExistentGroup');
|
||||||
|
|
||||||
|
response.statusCode = 404;
|
||||||
|
response.body = {
|
||||||
|
Error: { Code: 'NoSuchEntity', Message: 'Group "nonExistentGroup" does not exist' },
|
||||||
|
} as JsonObject;
|
||||||
|
|
||||||
|
await expect(handleError.call(mockExecuteSingleFunctions, data, response)).rejects.toThrow(
|
||||||
|
new NodeApiError(mockExecuteSingleFunctions.getNode(), response.body as JsonObject, {
|
||||||
|
message: 'Group "nonExistentGroup" does not exist',
|
||||||
|
description: ERROR_DESCRIPTIONS.NoSuchEntity.Group,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should throw NodeApiError for DeleteConflict', async () => {
|
||||||
|
mockExecuteSingleFunctions.getNodeParameter
|
||||||
|
.mockReturnValueOnce('user')
|
||||||
|
.mockReturnValue('userInGroup');
|
||||||
|
|
||||||
|
response.statusCode = 400;
|
||||||
|
response.body = {
|
||||||
|
Error: { Code: 'DeleteConflict', Message: 'User "userIngroup" is in a group' },
|
||||||
|
} as JsonObject;
|
||||||
|
|
||||||
|
await expect(handleError.call(mockExecuteSingleFunctions, data, response)).rejects.toThrow(
|
||||||
|
new NodeApiError(mockExecuteSingleFunctions.getNode(), response.body as JsonObject, {
|
||||||
|
message: 'User "userIngroup" is in a group',
|
||||||
|
description: 'This entity is still in use. Remove users from the group before deleting.',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
421
packages/nodes-base/nodes/Aws/IAM/test/helpers/utils.test.ts
Normal file
421
packages/nodes-base/nodes/Aws/IAM/test/helpers/utils.test.ts
Normal file
@@ -0,0 +1,421 @@
|
|||||||
|
import type { IHttpRequestOptions } from 'n8n-workflow';
|
||||||
|
import { NodeOperationError } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
preprocessTags,
|
||||||
|
deleteGroupMembers,
|
||||||
|
validatePath,
|
||||||
|
validateUserPath,
|
||||||
|
removeUserFromGroups,
|
||||||
|
findUsersForGroup,
|
||||||
|
simplifyGetGroupsResponse,
|
||||||
|
simplifyGetAllGroupsResponse,
|
||||||
|
simplifyGetAllUsersResponse,
|
||||||
|
validateName,
|
||||||
|
validatePermissionsBoundary,
|
||||||
|
encodeBodyAsFormUrlEncoded,
|
||||||
|
} from '../../helpers/utils';
|
||||||
|
import { awsApiRequest } from '../../transport';
|
||||||
|
|
||||||
|
jest.mock('../../transport', () => ({
|
||||||
|
awsApiRequest: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('AWS IAM - Helper Functions', () => {
|
||||||
|
let mockNode: any;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockNode = {
|
||||||
|
getNodeParameter: jest.fn(),
|
||||||
|
getNode: jest.fn(),
|
||||||
|
helpers: {
|
||||||
|
returnJsonArray: jest.fn((input: unknown[]) => input.map((i) => ({ json: i }))),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('encodeBodyAsFormUrlEncoded', () => {
|
||||||
|
it('should encode the body as application/x-www-form-urlencoded', async () => {
|
||||||
|
const requestOptions: IHttpRequestOptions = {
|
||||||
|
body: {
|
||||||
|
client_id: 'myClient',
|
||||||
|
client_secret: 'mySecret',
|
||||||
|
grant_type: 'client_credentials',
|
||||||
|
},
|
||||||
|
url: '',
|
||||||
|
headers: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await encodeBodyAsFormUrlEncoded.call(mockNode, requestOptions);
|
||||||
|
|
||||||
|
expect(result.body).toBe(
|
||||||
|
'client_id=myClient&client_secret=mySecret&grant_type=client_credentials',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return unchanged options if no body is present', async () => {
|
||||||
|
const requestOptions: IHttpRequestOptions = { url: '', headers: {} };
|
||||||
|
const result = await encodeBodyAsFormUrlEncoded.call(mockNode, requestOptions);
|
||||||
|
|
||||||
|
expect(result).toEqual({ url: '', headers: {} });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findUsersForGroup', () => {
|
||||||
|
it('should return users for a valid groupName', async () => {
|
||||||
|
mockNode.getNodeParameter.mockReturnValue('groupName');
|
||||||
|
const mockResponse = {
|
||||||
|
GetGroupResponse: { GetGroupResult: { Users: [{ UserName: 'user1' }] } },
|
||||||
|
};
|
||||||
|
(awsApiRequest as jest.Mock).mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
const result = await findUsersForGroup.call(mockNode, 'groupName');
|
||||||
|
expect(result).toEqual([{ UserName: 'user1' }]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('preprocessTags', () => {
|
||||||
|
it('should preprocess tags correctly into request body', async () => {
|
||||||
|
mockNode.getNodeParameter.mockReturnValue({
|
||||||
|
tags: [
|
||||||
|
{ key: 'Department', value: 'Engineering' },
|
||||||
|
{ key: 'Role', value: 'Developer' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const requestOptions = { body: '', headers: {}, url: '' };
|
||||||
|
const result = await preprocessTags.call(mockNode, requestOptions);
|
||||||
|
|
||||||
|
expect(result.body).toBe(
|
||||||
|
'Tags.member.1.Key=Department&Tags.member.1.Value=Engineering&Tags.member.2.Key=Role&Tags.member.2.Value=Developer',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error if a tag is missing a key', async () => {
|
||||||
|
mockNode.getNodeParameter.mockReturnValue({
|
||||||
|
tags: [{ key: '', value: 'Engineering' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const requestOptions = { body: '', headers: {}, url: '' };
|
||||||
|
|
||||||
|
await expect(preprocessTags.call(mockNode, requestOptions)).rejects.toThrow(
|
||||||
|
NodeOperationError,
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(preprocessTags.call(mockNode, requestOptions)).rejects.toThrow(
|
||||||
|
"Tag at position 1 is missing 'Key'. Both 'Key' and 'Value' are required.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error if a tag is missing a value', async () => {
|
||||||
|
mockNode.getNodeParameter.mockReturnValue({
|
||||||
|
tags: [{ key: 'Department', value: '' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const requestOptions = { body: '', headers: {}, url: '' };
|
||||||
|
|
||||||
|
await expect(preprocessTags.call(mockNode, requestOptions)).rejects.toThrow(
|
||||||
|
NodeOperationError,
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(preprocessTags.call(mockNode, requestOptions)).rejects.toThrow(
|
||||||
|
"Tag at position 1 is missing 'Value'. Both 'Key' and 'Value' are required.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deleteGroupMembers', () => {
|
||||||
|
it('should attempt to remove users from a group', async () => {
|
||||||
|
mockNode.getNodeParameter.mockImplementation((param: string) => {
|
||||||
|
if (param === 'group') {
|
||||||
|
return 'groupName';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockUsers = [{ UserName: 'user1' }];
|
||||||
|
(awsApiRequest as jest.Mock).mockResolvedValue(mockUsers);
|
||||||
|
|
||||||
|
const requestOptions = { headers: {}, url: '' };
|
||||||
|
const result = await deleteGroupMembers.call(mockNode, requestOptions);
|
||||||
|
|
||||||
|
expect(result).toEqual(requestOptions);
|
||||||
|
|
||||||
|
expect(awsApiRequest).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
method: 'POST',
|
||||||
|
body: expect.stringContaining('GroupName=groupName'),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validatePath', () => {
|
||||||
|
it('should throw an error for invalid path length', async () => {
|
||||||
|
mockNode.getNodeParameter.mockReturnValue('');
|
||||||
|
await expect(validatePath.call(mockNode, { headers: {}, url: '' })).rejects.toThrowError(
|
||||||
|
NodeOperationError,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error for invalid path format', async () => {
|
||||||
|
mockNode.getNodeParameter.mockReturnValue('/invalidPath');
|
||||||
|
await expect(validatePath.call(mockNode, { url: '' })).rejects.toThrowError(
|
||||||
|
NodeOperationError,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass for a valid path', async () => {
|
||||||
|
mockNode.getNodeParameter.mockReturnValue('/valid/path/');
|
||||||
|
const result = await validatePath.call(mockNode, { headers: {}, url: '' });
|
||||||
|
expect(result).toEqual({ headers: {}, url: '' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validateUserPath', () => {
|
||||||
|
it('should throw an error for invalid path prefix', async () => {
|
||||||
|
mockNode.getNodeParameter.mockReturnValue('/invalidPrefix');
|
||||||
|
|
||||||
|
const mockResponse = {
|
||||||
|
ListUsersResponse: {
|
||||||
|
ListUsersResult: {
|
||||||
|
Users: [{ UserName: 'user1', Path: '/validPrefix/user1' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
(awsApiRequest as jest.Mock).mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
await expect(validateUserPath.call(mockNode, { headers: {}, url: '' })).rejects.toThrowError(
|
||||||
|
NodeOperationError,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should modify the request body with a valid path', async () => {
|
||||||
|
mockNode.getNodeParameter.mockReturnValue('/validPrefix/');
|
||||||
|
const requestOptions = { body: {}, headers: {}, url: '' };
|
||||||
|
|
||||||
|
const mockResponse = {
|
||||||
|
ListUsersResponse: {
|
||||||
|
ListUsersResult: {
|
||||||
|
Users: [{ UserName: 'user1', Path: '/validPrefix/user1' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
(awsApiRequest as jest.Mock).mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
const result = await validateUserPath.call(mockNode, requestOptions);
|
||||||
|
expect(result.body).toHaveProperty('PathPrefix', '/validPrefix/');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validateName', () => {
|
||||||
|
const requestOptions: IHttpRequestOptions = { body: {}, url: '' };
|
||||||
|
|
||||||
|
it('should throw an error if userName contains spaces', async () => {
|
||||||
|
mockNode.getNodeParameter.mockImplementation((param: string) => {
|
||||||
|
if (param === 'resource') return 'user';
|
||||||
|
if (param === 'userName') return 'John Doe';
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(validateName.call(mockNode, requestOptions)).rejects.toThrowError(
|
||||||
|
new NodeOperationError(mockNode.getNode(), 'User name should not contain spaces.'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error if userName contains invalid characters', async () => {
|
||||||
|
mockNode.getNodeParameter.mockImplementation((param: string) => {
|
||||||
|
if (param === 'resource') return 'user';
|
||||||
|
if (param === 'userName') return 'John@Doe';
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(validateName.call(mockNode, requestOptions)).rejects.toThrowError(
|
||||||
|
new NodeOperationError(
|
||||||
|
mockNode.getNode(),
|
||||||
|
'User name can have up to 64 characters. Valid characters: letters, numbers, hyphens (-), and underscores (_).',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass validation for valid userName', async () => {
|
||||||
|
mockNode.getNodeParameter.mockImplementation((param: string) => {
|
||||||
|
if (param === 'resource') return 'user';
|
||||||
|
if (param === 'userName') return 'John_Doe123';
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(validateName.call(mockNode, requestOptions)).resolves.toEqual(requestOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error if groupName contains spaces', async () => {
|
||||||
|
mockNode.getNodeParameter.mockImplementation((param: string) => {
|
||||||
|
if (param === 'resource') return 'group';
|
||||||
|
if (param === 'groupName') return 'Group Name';
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(validateName.call(mockNode, requestOptions)).rejects.toThrowError(
|
||||||
|
new NodeOperationError(mockNode.getNode(), 'Group name should not contain spaces.'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error if groupName contains invalid characters', async () => {
|
||||||
|
mockNode.getNodeParameter.mockImplementation((param: string) => {
|
||||||
|
if (param === 'resource') return 'group';
|
||||||
|
if (param === 'groupName') return 'Group@Name';
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(validateName.call(mockNode, requestOptions)).rejects.toThrowError(
|
||||||
|
new NodeOperationError(
|
||||||
|
mockNode.getNode(),
|
||||||
|
'Group name can have up to 128 characters. Valid characters: letters, numbers, hyphens (-), and underscores (_).',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass validation for valid groupName', async () => {
|
||||||
|
mockNode.getNodeParameter.mockImplementation((param: string) => {
|
||||||
|
if (param === 'resource') return 'group';
|
||||||
|
if (param === 'groupName') return 'Group_Name-123';
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(validateName.call(mockNode, requestOptions)).resolves.toEqual(requestOptions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validatePermissionsBoundary', () => {
|
||||||
|
const requestOptions: IHttpRequestOptions = { body: {}, url: '' };
|
||||||
|
|
||||||
|
it('should return the request options unchanged if no permissions boundary is set', async () => {
|
||||||
|
mockNode.getNodeParameter.mockReturnValue(undefined);
|
||||||
|
|
||||||
|
const result = await validatePermissionsBoundary.call(mockNode, requestOptions);
|
||||||
|
|
||||||
|
expect(result).toEqual(requestOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add a valid permissions boundary to the request body', async () => {
|
||||||
|
const validArn = 'arn:aws:iam::123456789012:policy/ExamplePolicy';
|
||||||
|
mockNode.getNodeParameter.mockReturnValue(validArn);
|
||||||
|
|
||||||
|
const result = await validatePermissionsBoundary.call(mockNode, requestOptions);
|
||||||
|
|
||||||
|
expect(result.body).toEqual({ PermissionsBoundary: validArn });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error for invalid permissions boundary format', async () => {
|
||||||
|
const invalidArn = 'invalid:arn:format';
|
||||||
|
mockNode.getNodeParameter.mockReturnValue(invalidArn);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
validatePermissionsBoundary.call(mockNode, { body: {}, url: '', headers: {} }),
|
||||||
|
).rejects.toThrow(NodeOperationError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('simplifyGetGroupsResponse', () => {
|
||||||
|
it('should return group data', async () => {
|
||||||
|
mockNode.getNodeParameter.mockReturnValue(false);
|
||||||
|
const mockResponse = {
|
||||||
|
body: { GetGroupResponse: { GetGroupResult: { Group: { GroupName: 'TestGroup' } } } },
|
||||||
|
headers: {},
|
||||||
|
url: '',
|
||||||
|
statusCode: 200,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await simplifyGetGroupsResponse.call(mockNode, [], mockResponse);
|
||||||
|
|
||||||
|
expect(result).toEqual([{ json: { GroupName: 'TestGroup' } }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include users if "includeUsers" is true', async () => {
|
||||||
|
mockNode.getNodeParameter.mockReturnValue(true);
|
||||||
|
const mockResponse = {
|
||||||
|
body: {
|
||||||
|
GetGroupResponse: {
|
||||||
|
GetGroupResult: { Group: { GroupName: 'TestGroup' }, Users: [{ UserName: 'user1' }] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
headers: {},
|
||||||
|
url: '',
|
||||||
|
statusCode: 200,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await simplifyGetGroupsResponse.call(mockNode, [], mockResponse);
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
{ json: { GroupName: 'TestGroup', Users: [{ UserName: 'user1' }] } },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('simplifyGetAllGroupsResponse', () => {
|
||||||
|
it('should return groups without users if "includeUsers" is false', async () => {
|
||||||
|
mockNode.getNodeParameter.mockReturnValue(false);
|
||||||
|
const mockResponse = {
|
||||||
|
body: {
|
||||||
|
ListGroupsResponse: { ListGroupsResult: { Groups: [{ GroupName: 'TestGroup' }] } },
|
||||||
|
},
|
||||||
|
headers: {},
|
||||||
|
url: '',
|
||||||
|
statusCode: 200,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await simplifyGetAllGroupsResponse.call(mockNode, [], mockResponse);
|
||||||
|
|
||||||
|
expect(result).toEqual([{ json: { GroupName: 'TestGroup' } }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return groups with users if "includeUsers" is true', async () => {
|
||||||
|
mockNode.getNodeParameter.mockReturnValue(true);
|
||||||
|
const mockResponse = {
|
||||||
|
body: {
|
||||||
|
ListGroupsResponse: { ListGroupsResult: { Groups: [{ GroupName: 'TestGroup' }] } },
|
||||||
|
},
|
||||||
|
headers: {},
|
||||||
|
url: '',
|
||||||
|
statusCode: 200,
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockUsers = [{ UserName: 'user1' }];
|
||||||
|
(awsApiRequest as jest.Mock).mockResolvedValueOnce({
|
||||||
|
GetGroupResponse: { GetGroupResult: { Users: mockUsers } },
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await simplifyGetAllGroupsResponse.call(mockNode, [], mockResponse);
|
||||||
|
|
||||||
|
expect(result).toEqual([{ json: { GroupName: 'TestGroup', Users: mockUsers } }]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('simplifyGetAllUsersResponse', () => {
|
||||||
|
it('should return all users', async () => {
|
||||||
|
const mockResponse = {
|
||||||
|
body: { ListUsersResponse: { ListUsersResult: { Users: [{ UserName: 'user1' }] } } },
|
||||||
|
headers: {},
|
||||||
|
url: '',
|
||||||
|
statusCode: 200,
|
||||||
|
};
|
||||||
|
const result = await simplifyGetAllUsersResponse.call(mockNode, [], mockResponse);
|
||||||
|
expect(result).toEqual([{ json: { UserName: 'user1' } }]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('removeUserFromGroups', () => {
|
||||||
|
it('should remove a user from all groups', async () => {
|
||||||
|
mockNode.getNodeParameter.mockReturnValue('user1');
|
||||||
|
const mockUserGroups = { results: [{ value: 'group1' }, { value: 'group2' }] };
|
||||||
|
(awsApiRequest as jest.Mock).mockResolvedValue(mockUserGroups);
|
||||||
|
|
||||||
|
const requestOptions = { headers: {}, url: '' };
|
||||||
|
const result = await removeUserFromGroups.call(mockNode, requestOptions);
|
||||||
|
|
||||||
|
expect(result).toEqual(requestOptions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
import type { ILoadOptionsFunctions } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { searchUsers, searchGroups, searchGroupsForUser } from '../../methods/listSearch';
|
||||||
|
import { awsApiRequest } from '../../transport';
|
||||||
|
|
||||||
|
jest.mock('../../transport', () => ({
|
||||||
|
awsApiRequest: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('AWS IAM - List search', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockContext = {
|
||||||
|
helpers: {
|
||||||
|
requestWithAuthentication: jest.fn(),
|
||||||
|
},
|
||||||
|
getNodeParameter: jest.fn(),
|
||||||
|
getCredentials: jest.fn(),
|
||||||
|
} as unknown as ILoadOptionsFunctions;
|
||||||
|
|
||||||
|
describe('searchUsers', () => {
|
||||||
|
it('should return an empty result if no users are found', async () => {
|
||||||
|
const responseData = { ListUsersResponse: { ListUsersResult: { Users: [] } } };
|
||||||
|
(awsApiRequest as jest.Mock).mockResolvedValue(responseData);
|
||||||
|
|
||||||
|
const result = await searchUsers.call(mockContext);
|
||||||
|
expect(result.results).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return formatted user results when users are found', async () => {
|
||||||
|
const responseData = {
|
||||||
|
ListUsersResponse: {
|
||||||
|
ListUsersResult: {
|
||||||
|
Users: [{ UserName: 'User1' }, { UserName: 'User2' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
(awsApiRequest as jest.Mock).mockResolvedValue(responseData);
|
||||||
|
|
||||||
|
const result = await searchUsers.call(mockContext);
|
||||||
|
expect(result.results).toEqual([
|
||||||
|
{ name: 'User1', value: 'User1' },
|
||||||
|
{ name: 'User2', value: 'User2' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should apply filter to the user results', async () => {
|
||||||
|
const responseData = {
|
||||||
|
ListUsersResponse: {
|
||||||
|
ListUsersResult: {
|
||||||
|
Users: [{ UserName: 'User1' }, { UserName: 'User2' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
(awsApiRequest as jest.Mock).mockResolvedValue(responseData);
|
||||||
|
|
||||||
|
const result = await searchUsers.call(mockContext, 'User1');
|
||||||
|
expect(result.results).toEqual([{ name: 'User1', value: 'User1' }]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('searchGroups', () => {
|
||||||
|
it('should return an empty result if no groups are found', async () => {
|
||||||
|
const responseData = { ListGroupsResponse: { ListGroupsResult: { Groups: [] } } };
|
||||||
|
(awsApiRequest as jest.Mock).mockResolvedValue(responseData);
|
||||||
|
|
||||||
|
const result = await searchGroups.call(mockContext);
|
||||||
|
expect(result.results).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return formatted group results when groups are found', async () => {
|
||||||
|
const responseData = {
|
||||||
|
ListGroupsResponse: {
|
||||||
|
ListGroupsResult: {
|
||||||
|
Groups: [{ GroupName: 'Group1' }, { GroupName: 'Group2' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
(awsApiRequest as jest.Mock).mockResolvedValue(responseData);
|
||||||
|
|
||||||
|
const result = await searchGroups.call(mockContext);
|
||||||
|
expect(result.results).toEqual([
|
||||||
|
{ name: 'Group1', value: 'Group1' },
|
||||||
|
{ name: 'Group2', value: 'Group2' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should apply filter to the group results', async () => {
|
||||||
|
const responseData = {
|
||||||
|
ListGroupsResponse: {
|
||||||
|
ListGroupsResult: {
|
||||||
|
Groups: [{ GroupName: 'Group1' }, { GroupName: 'Group2' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
(awsApiRequest as jest.Mock).mockResolvedValue(responseData);
|
||||||
|
|
||||||
|
const result = await searchGroups.call(mockContext, 'Group1');
|
||||||
|
expect(result.results).toEqual([{ name: 'Group1', value: 'Group1' }]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('searchGroupsForUser', () => {
|
||||||
|
it('should return empty if no user groups are found', async () => {
|
||||||
|
mockContext.getNodeParameter = jest.fn().mockReturnValue('user1');
|
||||||
|
|
||||||
|
const responseData = {
|
||||||
|
ListGroupsResponse: { ListGroupsResult: { Groups: [] } },
|
||||||
|
};
|
||||||
|
(awsApiRequest as jest.Mock).mockResolvedValue(responseData);
|
||||||
|
|
||||||
|
const result = await searchGroupsForUser.call(mockContext);
|
||||||
|
|
||||||
|
expect(result.results).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||||
|
import nock from 'nock';
|
||||||
|
|
||||||
|
import { CURRENT_VERSION } from '../../helpers/constants';
|
||||||
|
|
||||||
|
describe('AWS IAM - Add User to Group', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const baseUrl = 'https://iam.amazonaws.com/';
|
||||||
|
nock.cleanAll();
|
||||||
|
nock(baseUrl)
|
||||||
|
.persist()
|
||||||
|
.defaultReplyHeaders({ 'Content-Type': 'application/x-amz-json-1.1' })
|
||||||
|
.post('/', {
|
||||||
|
Action: 'AddUserToGroup',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
UserName: 'Jonas',
|
||||||
|
GroupName: 'GroupNameUpdated2',
|
||||||
|
})
|
||||||
|
.reply(200, {
|
||||||
|
AddUserToGroupResponse: {
|
||||||
|
ResponseMetadata: {
|
||||||
|
RequestId: '8192250c-9225-4903-af62-a521ce939968',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new NodeTestHarness().setupTests({
|
||||||
|
workflowFiles: ['addToGroup.workflow.json'],
|
||||||
|
credentials: {
|
||||||
|
aws: {
|
||||||
|
region: 'eu-central-1',
|
||||||
|
accessKeyId: 'test',
|
||||||
|
secretAccessKey: 'test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [-40, -120],
|
||||||
|
"id": "7da2ce49-9a9d-4240-b082-ff1b12d101b1",
|
||||||
|
"name": "When clicking ‘Test workflow’"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"operation": "addToGroup",
|
||||||
|
"user": {
|
||||||
|
"__rl": true,
|
||||||
|
"value": "Jonas",
|
||||||
|
"mode": "list",
|
||||||
|
"cachedResultName": "Jonas"
|
||||||
|
},
|
||||||
|
"group": {
|
||||||
|
"__rl": true,
|
||||||
|
"value": "GroupNameUpdated2",
|
||||||
|
"mode": "list",
|
||||||
|
"cachedResultName": "GroupNameUpdated2"
|
||||||
|
},
|
||||||
|
"requestOptions": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.awsIam",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [160, -120],
|
||||||
|
"id": "982d8c6e-e94a-414c-90ba-35743676eef8",
|
||||||
|
"name": "addToGroup",
|
||||||
|
"credentials": {
|
||||||
|
"aws": {
|
||||||
|
"id": "exampleId",
|
||||||
|
"name": "AWS US EAST"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {
|
||||||
|
"addToGroup": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"AddUserToGroupResponse": {
|
||||||
|
"ResponseMetadata": {
|
||||||
|
"RequestId": "8192250c-9225-4903-af62-a521ce939968"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"When clicking ‘Test workflow’": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "addToGroup",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"versionId": "2e9e5c7a-705f-49a7-83db-397f1bb48798",
|
||||||
|
"meta": {
|
||||||
|
"templateCredsSetupCompleted": true,
|
||||||
|
"instanceId": "26f38eb23ad84214537831cfef4032299ffac994ff18d5cb72e82d31ac4ceac4"
|
||||||
|
},
|
||||||
|
"id": "0qqycx08fTBpfbC9",
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
47
packages/nodes-base/nodes/Aws/IAM/test/user/create.test.ts
Normal file
47
packages/nodes-base/nodes/Aws/IAM/test/user/create.test.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||||
|
import nock from 'nock';
|
||||||
|
|
||||||
|
import { BASE_URL, CURRENT_VERSION } from '../../helpers/constants';
|
||||||
|
|
||||||
|
describe('AWS IAM - Create user', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
nock.cleanAll();
|
||||||
|
nock(BASE_URL)
|
||||||
|
.persist()
|
||||||
|
.defaultReplyHeaders({ 'Content-Type': 'application/x-amz-json-1.1' })
|
||||||
|
.post('/', {
|
||||||
|
Action: 'CreateUser',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
UserName: 'UserTest1',
|
||||||
|
})
|
||||||
|
.reply(200, {
|
||||||
|
CreateUserResponse: {
|
||||||
|
CreateUserResult: {
|
||||||
|
User: {
|
||||||
|
Arn: 'arn:aws:iam::130450532146:user/UserTest1',
|
||||||
|
CreateDate: 1744115235,
|
||||||
|
PasswordLastUsed: null,
|
||||||
|
Path: '/',
|
||||||
|
PermissionsBoundary: null,
|
||||||
|
UserId: 'AIDAR4X3VE4ZHHMNF7NBB',
|
||||||
|
UserName: 'UserTest1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ResponseMetadata: {
|
||||||
|
RequestId: 'ce14481c-5629-4ae4-9eae-3722f48bb3e0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new NodeTestHarness().setupTests({
|
||||||
|
workflowFiles: ['create.workflow.json'],
|
||||||
|
credentials: {
|
||||||
|
aws: {
|
||||||
|
region: 'eu-central-1',
|
||||||
|
accessKeyId: 'test',
|
||||||
|
secretAccessKey: 'test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [200, 120],
|
||||||
|
"id": "b4205abf-7102-4e53-8aed-7bd047acfaf4",
|
||||||
|
"name": "When clicking ‘Test workflow’"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"operation": "create",
|
||||||
|
"userName": "UserTest1",
|
||||||
|
"additionalFields": {
|
||||||
|
"path": "/new/path/"
|
||||||
|
},
|
||||||
|
"requestOptions": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.awsIam",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [400, 120],
|
||||||
|
"id": "64bbfce7-cc7d-4e69-82d3-d4ecac5ad389",
|
||||||
|
"name": "AWS IAM12",
|
||||||
|
"credentials": {
|
||||||
|
"aws": {
|
||||||
|
"id": "exampleId",
|
||||||
|
"name": "AWS US EAST"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"connections": {
|
||||||
|
"When clicking ‘Test workflow’": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "AWS IAM12",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pinData": {
|
||||||
|
"AWS IAM12": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"CreateUserResponse": {
|
||||||
|
"CreateUserResult": {
|
||||||
|
"User": {
|
||||||
|
"Arn": "arn:aws:iam::130450532146:user/UserTest1",
|
||||||
|
"CreateDate": 1744115235,
|
||||||
|
"PasswordLastUsed": null,
|
||||||
|
"Path": "/",
|
||||||
|
"PermissionsBoundary": null,
|
||||||
|
"UserId": "AIDAR4X3VE4ZHHMNF7NBB",
|
||||||
|
"UserName": "UserTest1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ResponseMetadata": {
|
||||||
|
"RequestId": "ce14481c-5629-4ae4-9eae-3722f48bb3e0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
96
packages/nodes-base/nodes/Aws/IAM/test/user/delete.test.ts
Normal file
96
packages/nodes-base/nodes/Aws/IAM/test/user/delete.test.ts
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||||
|
import nock from 'nock';
|
||||||
|
|
||||||
|
import { BASE_URL, CURRENT_VERSION } from '../../helpers/constants';
|
||||||
|
|
||||||
|
describe('AWS IAM - Delete user', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
nock.cleanAll();
|
||||||
|
nock(BASE_URL)
|
||||||
|
.persist()
|
||||||
|
.defaultReplyHeaders({ 'Content-Type': 'application/x-amz-json-1.1' })
|
||||||
|
.post('/', {
|
||||||
|
Action: 'GetUser',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
UserName: 'JohnThis10',
|
||||||
|
})
|
||||||
|
.reply(200, {
|
||||||
|
GetUserResponse: {
|
||||||
|
GetUserResult: {
|
||||||
|
User: {
|
||||||
|
UserName: 'JohnThis10',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
nock(BASE_URL)
|
||||||
|
.persist()
|
||||||
|
.defaultReplyHeaders({ 'Content-Type': 'application/x-amz-json-1.1' })
|
||||||
|
.post('/', {
|
||||||
|
Action: 'ListGroups',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
})
|
||||||
|
.reply(200, {
|
||||||
|
ListGroupsResponse: {
|
||||||
|
ListGroupsResult: {
|
||||||
|
Groups: [{ GroupName: 'GroupA' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
nock(BASE_URL)
|
||||||
|
.persist()
|
||||||
|
.defaultReplyHeaders({ 'Content-Type': 'application/x-amz-json-1.1' })
|
||||||
|
.post('/', {
|
||||||
|
Action: 'GetGroup',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
GroupName: 'GroupA',
|
||||||
|
})
|
||||||
|
.reply(200, {
|
||||||
|
GetGroupResponse: {
|
||||||
|
GetGroupResult: {
|
||||||
|
Users: [{ UserName: 'JohnThis10' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
nock(BASE_URL)
|
||||||
|
.persist()
|
||||||
|
.defaultReplyHeaders({ 'Content-Type': 'application/x-amz-json-1.1' })
|
||||||
|
.post('/', {
|
||||||
|
Action: 'RemoveUserFromGroup',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
GroupName: 'GroupA',
|
||||||
|
UserName: 'JohnThis10',
|
||||||
|
})
|
||||||
|
.reply(200, {
|
||||||
|
ResponseMetadata: {
|
||||||
|
RequestId: 'remove-groupA-id',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
nock(BASE_URL)
|
||||||
|
.persist()
|
||||||
|
.defaultReplyHeaders({ 'Content-Type': 'application/x-amz-json-1.1' })
|
||||||
|
.post('/', {
|
||||||
|
Action: 'DeleteUser',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
UserName: 'JohnThis10',
|
||||||
|
})
|
||||||
|
.reply(200, {
|
||||||
|
DeleteUserResponse: {
|
||||||
|
ResponseMetadata: {
|
||||||
|
RequestId: '44c7c6c0-260b-4dfd-beee-2cce8f05bed3',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new NodeTestHarness().setupTests({
|
||||||
|
workflowFiles: ['delete.workflow.json'],
|
||||||
|
credentials: {
|
||||||
|
aws: {
|
||||||
|
region: 'eu-central-1',
|
||||||
|
accessKeyId: 'test',
|
||||||
|
secretAccessKey: 'test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [-80, -100],
|
||||||
|
"id": "7da2ce49-9a9d-4240-b082-ff1b12d101b1",
|
||||||
|
"name": "When clicking ‘Test workflow’"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"operation": "delete",
|
||||||
|
"user": {
|
||||||
|
"__rl": true,
|
||||||
|
"value": "JohnThis10",
|
||||||
|
"mode": "list",
|
||||||
|
"cachedResultName": "JohnThis10"
|
||||||
|
},
|
||||||
|
"requestOptions": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.awsIam",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [180, -100],
|
||||||
|
"id": "f8bf0d9d-78aa-48e6-80bd-3018c0bac0ea",
|
||||||
|
"name": "deleteUser",
|
||||||
|
"credentials": {
|
||||||
|
"aws": {
|
||||||
|
"id": "exampleId",
|
||||||
|
"name": "AWS US EAST"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {
|
||||||
|
"deleteUser": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"DeleteUserResponse": {
|
||||||
|
"ResponseMetadata": {
|
||||||
|
"RequestId": "44c7c6c0-260b-4dfd-beee-2cce8f05bed3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"When clicking ‘Test workflow’": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "deleteUser",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"versionId": "731b81df-c6a0-4074-a3f8-e0ad0e9c884a",
|
||||||
|
"meta": {
|
||||||
|
"templateCredsSetupCompleted": true,
|
||||||
|
"instanceId": "26f38eb23ad84214537831cfef4032299ffac994ff18d5cb72e82d31ac4ceac4"
|
||||||
|
},
|
||||||
|
"id": "0qqycx08fTBpfbC9",
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
49
packages/nodes-base/nodes/Aws/IAM/test/user/get.test.ts
Normal file
49
packages/nodes-base/nodes/Aws/IAM/test/user/get.test.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||||
|
import nock from 'nock';
|
||||||
|
|
||||||
|
import { BASE_URL, CURRENT_VERSION } from '../../helpers/constants';
|
||||||
|
|
||||||
|
describe('AWS IAM - Get User', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
nock(BASE_URL)
|
||||||
|
.persist()
|
||||||
|
.defaultReplyHeaders({ 'Content-Type': 'application/x-amz-json-1.1' })
|
||||||
|
.post('/', {
|
||||||
|
Action: 'GetUser',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
UserName: 'accounts@this.de',
|
||||||
|
})
|
||||||
|
.reply(200, {
|
||||||
|
GetUserResponse: {
|
||||||
|
GetUserResult: {
|
||||||
|
User: {
|
||||||
|
UserName: 'accounts@this.de',
|
||||||
|
UserId: 'AIDAR4X3VE4ZANWXRN2L2',
|
||||||
|
Arn: 'arn:aws:iam::130450532146:user/accounts@this.de',
|
||||||
|
CreateDate: 1733911052,
|
||||||
|
Path: '/',
|
||||||
|
Tags: [
|
||||||
|
{
|
||||||
|
Key: 'AKIAR4X3VE4ZALQYFEMT',
|
||||||
|
Value: 'API dev',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
PasswordLastUsed: null,
|
||||||
|
PermissionsBoundary: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new NodeTestHarness().setupTests({
|
||||||
|
workflowFiles: ['get.workflow.json'],
|
||||||
|
credentials: {
|
||||||
|
aws: {
|
||||||
|
region: 'eu-central-1',
|
||||||
|
accessKeyId: 'test',
|
||||||
|
secretAccessKey: 'test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [-40, -120],
|
||||||
|
"id": "7da2ce49-9a9d-4240-b082-ff1b12d101b1",
|
||||||
|
"name": "When clicking ‘Test workflow’"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"operation": "get",
|
||||||
|
"user": {
|
||||||
|
"__rl": true,
|
||||||
|
"mode": "list",
|
||||||
|
"value": "accounts@this.de"
|
||||||
|
},
|
||||||
|
"requestOptions": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.awsIam",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [140, -100],
|
||||||
|
"id": "982d8c6e-e94a-414c-90ba-35743676eef8",
|
||||||
|
"name": "getUser",
|
||||||
|
"credentials": {
|
||||||
|
"aws": {
|
||||||
|
"id": "exampleId",
|
||||||
|
"name": "AWS US EAST"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {
|
||||||
|
"getUser": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"Arn": "arn:aws:iam::130450532146:user/accounts@this.de",
|
||||||
|
"CreateDate": 1733911052,
|
||||||
|
"PasswordLastUsed": null,
|
||||||
|
"Path": "/",
|
||||||
|
"PermissionsBoundary": null,
|
||||||
|
"Tags": [
|
||||||
|
{
|
||||||
|
"Key": "AKIAR4X3VE4ZALQYFEMT",
|
||||||
|
"Value": "API dev"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"UserId": "AIDAR4X3VE4ZANWXRN2L2",
|
||||||
|
"UserName": "accounts@this.de"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"When clicking ‘Test workflow’": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "getUser",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"versionId": "5ce54c9d-de6e-499a-b042-0bb89c41208c",
|
||||||
|
"meta": {
|
||||||
|
"templateCredsSetupCompleted": true,
|
||||||
|
"instanceId": "26f38eb23ad84214537831cfef4032299ffac994ff18d5cb72e82d31ac4ceac4"
|
||||||
|
},
|
||||||
|
"id": "0qqycx08fTBpfbC9",
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
57
packages/nodes-base/nodes/Aws/IAM/test/user/getAll.test.ts
Normal file
57
packages/nodes-base/nodes/Aws/IAM/test/user/getAll.test.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||||
|
import nock from 'nock';
|
||||||
|
|
||||||
|
import { BASE_URL, CURRENT_VERSION } from '../../helpers/constants';
|
||||||
|
|
||||||
|
describe('AWS IAM - Get All Users', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
nock.cleanAll();
|
||||||
|
nock(BASE_URL)
|
||||||
|
.persist()
|
||||||
|
.defaultReplyHeaders({ 'Content-Type': 'application/x-amz-json-1.1' })
|
||||||
|
.post('/', {
|
||||||
|
Action: 'ListUsers',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
MaxItems: 100,
|
||||||
|
})
|
||||||
|
.reply(200, {
|
||||||
|
ListUsersResponse: {
|
||||||
|
ListUsersResult: {
|
||||||
|
Users: [
|
||||||
|
{
|
||||||
|
Arn: 'arn:aws:iam::130450532146:user/Johnnn',
|
||||||
|
UserName: 'Johnnn',
|
||||||
|
UserId: 'AIDAR4X3VE4ZAJGXLCVOP',
|
||||||
|
Path: '/',
|
||||||
|
CreateDate: 1739198010,
|
||||||
|
PasswordLastUsed: null,
|
||||||
|
PermissionsBoundary: null,
|
||||||
|
Tags: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Arn: 'arn:aws:iam::130450532146:user/rhis/path/Jonas',
|
||||||
|
UserName: 'Jonas',
|
||||||
|
UserId: 'AIDAR4X3VE4ZDJJFKI6OU',
|
||||||
|
Path: '/rhis/path/',
|
||||||
|
CreateDate: 1739198295,
|
||||||
|
PasswordLastUsed: null,
|
||||||
|
PermissionsBoundary: null,
|
||||||
|
Tags: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new NodeTestHarness().setupTests({
|
||||||
|
workflowFiles: ['getAll.workflow.json'],
|
||||||
|
credentials: {
|
||||||
|
aws: {
|
||||||
|
region: 'eu-central-1',
|
||||||
|
accessKeyId: 'test',
|
||||||
|
secretAccessKey: 'test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [-40, -220],
|
||||||
|
"id": "7da2ce49-9a9d-4240-b082-ff1b12d101b1",
|
||||||
|
"name": "When clicking ‘Test workflow’"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"additionalFields": {},
|
||||||
|
"requestOptions": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.awsIam",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [160, -220],
|
||||||
|
"id": "23fd4c79-516d-49dc-81c8-e68052a294d1",
|
||||||
|
"name": "getAllUsers",
|
||||||
|
"credentials": {
|
||||||
|
"aws": {
|
||||||
|
"id": "exampleId",
|
||||||
|
"name": "AWS US EAST"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {
|
||||||
|
"getAllUsers": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"Arn": "arn:aws:iam::130450532146:user/Johnnn",
|
||||||
|
"CreateDate": 1739198010,
|
||||||
|
"PasswordLastUsed": null,
|
||||||
|
"Path": "/",
|
||||||
|
"PermissionsBoundary": null,
|
||||||
|
"Tags": null,
|
||||||
|
"UserId": "AIDAR4X3VE4ZAJGXLCVOP",
|
||||||
|
"UserName": "Johnnn"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"Arn": "arn:aws:iam::130450532146:user/rhis/path/Jonas",
|
||||||
|
"CreateDate": 1739198295,
|
||||||
|
"PasswordLastUsed": null,
|
||||||
|
"Path": "/rhis/path/",
|
||||||
|
"PermissionsBoundary": null,
|
||||||
|
"Tags": null,
|
||||||
|
"UserId": "AIDAR4X3VE4ZDJJFKI6OU",
|
||||||
|
"UserName": "Jonas"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"When clicking ‘Test workflow’": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "getAllUsers",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"versionId": "c23e7348-6add-4711-b567-53922f951cbe",
|
||||||
|
"meta": {
|
||||||
|
"templateCredsSetupCompleted": true,
|
||||||
|
"instanceId": "26f38eb23ad84214537831cfef4032299ffac994ff18d5cb72e82d31ac4ceac4"
|
||||||
|
},
|
||||||
|
"id": "0qqycx08fTBpfbC9",
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||||
|
import nock from 'nock';
|
||||||
|
|
||||||
|
import { BASE_URL, CURRENT_VERSION } from '../../helpers/constants';
|
||||||
|
|
||||||
|
describe('AWS IAM - Remove User From Group', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
nock.cleanAll();
|
||||||
|
nock(BASE_URL)
|
||||||
|
.persist()
|
||||||
|
.defaultReplyHeaders({ 'Content-Type': 'application/x-amz-json-1.1' })
|
||||||
|
.post('/', {
|
||||||
|
Action: 'RemoveUserFromGroup',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
UserName: 'UserTest1',
|
||||||
|
GroupName: 'GroupCreatedAfter',
|
||||||
|
})
|
||||||
|
.reply(200, {
|
||||||
|
RemoveUserFromGroupResponse: {
|
||||||
|
ResponseMetadata: {
|
||||||
|
RequestId: '48508b51-1506-496c-8455-7135269209f0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new NodeTestHarness().setupTests({
|
||||||
|
workflowFiles: ['removeFromGroup.workflow.json'],
|
||||||
|
credentials: {
|
||||||
|
aws: {
|
||||||
|
region: 'eu-central-1',
|
||||||
|
accessKeyId: 'test',
|
||||||
|
secretAccessKey: 'test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [-40, -120],
|
||||||
|
"id": "7da2ce49-9a9d-4240-b082-ff1b12d101b1",
|
||||||
|
"name": "When clicking ‘Test workflow’"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"operation": "removeFromGroup",
|
||||||
|
"user": {
|
||||||
|
"__rl": true,
|
||||||
|
"value": "UserTest1",
|
||||||
|
"mode": "list",
|
||||||
|
"cachedResultName": "UserTest1"
|
||||||
|
},
|
||||||
|
"group": {
|
||||||
|
"__rl": true,
|
||||||
|
"value": "GroupCreatedAfter",
|
||||||
|
"mode": "list",
|
||||||
|
"cachedResultName": "GroupCreatedAfter"
|
||||||
|
},
|
||||||
|
"requestOptions": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.awsIam",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [140, -120],
|
||||||
|
"id": "5f05f1ad-aaae-49c1-b03d-c77c1442a0c9",
|
||||||
|
"name": "removeFromGroup",
|
||||||
|
"credentials": {
|
||||||
|
"aws": {
|
||||||
|
"id": "exampleId",
|
||||||
|
"name": "AWS US EAST"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {
|
||||||
|
"removeFromGroup": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"RemoveUserFromGroupResponse": {
|
||||||
|
"ResponseMetadata": {
|
||||||
|
"RequestId": "48508b51-1506-496c-8455-7135269209f0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"When clicking ‘Test workflow’": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "removeFromGroup",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"versionId": "9cb635bc-9e0f-4903-8705-c0ec4495c39f",
|
||||||
|
"meta": {
|
||||||
|
"templateCredsSetupCompleted": true,
|
||||||
|
"instanceId": "26f38eb23ad84214537831cfef4032299ffac994ff18d5cb72e82d31ac4ceac4"
|
||||||
|
},
|
||||||
|
"id": "0qqycx08fTBpfbC9",
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
37
packages/nodes-base/nodes/Aws/IAM/test/user/update.test.ts
Normal file
37
packages/nodes-base/nodes/Aws/IAM/test/user/update.test.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||||
|
import nock from 'nock';
|
||||||
|
|
||||||
|
import { BASE_URL, CURRENT_VERSION } from '../../helpers/constants';
|
||||||
|
|
||||||
|
describe('AWS IAM - Update User', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
nock.cleanAll();
|
||||||
|
nock(BASE_URL)
|
||||||
|
.persist()
|
||||||
|
.defaultReplyHeaders({ 'Content-Type': 'application/x-amz-json-1.1' })
|
||||||
|
.post('/', {
|
||||||
|
Action: 'UpdateUser',
|
||||||
|
Version: CURRENT_VERSION,
|
||||||
|
UserName: 'NewUser',
|
||||||
|
NewUserName: 'UserTest',
|
||||||
|
})
|
||||||
|
.reply(200, {
|
||||||
|
UpdateUserResponse: {
|
||||||
|
ResponseMetadata: {
|
||||||
|
RequestId: 'bdb4a8b5-627a-41a7-aba9-5733b7869c16',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new NodeTestHarness().setupTests({
|
||||||
|
workflowFiles: ['update.workflow.json'],
|
||||||
|
credentials: {
|
||||||
|
aws: {
|
||||||
|
region: 'eu-central-1',
|
||||||
|
accessKeyId: 'test',
|
||||||
|
secretAccessKey: 'test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [200, 120],
|
||||||
|
"id": "b4205abf-7102-4e53-8aed-7bd047acfaf4",
|
||||||
|
"name": "When clicking ‘Test workflow’"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"operation": "update",
|
||||||
|
"user": {
|
||||||
|
"__rl": true,
|
||||||
|
"value": "NewUser",
|
||||||
|
"mode": "list",
|
||||||
|
"cachedResultName": "NewUser"
|
||||||
|
},
|
||||||
|
"userName": "UserTest",
|
||||||
|
"requestOptions": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.awsIam",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [400, 120],
|
||||||
|
"id": "64bbfce7-cc7d-4e69-82d3-d4ecac5ad389",
|
||||||
|
"name": "AWS IAM12",
|
||||||
|
"credentials": {
|
||||||
|
"aws": {
|
||||||
|
"id": "exampleId",
|
||||||
|
"name": "AWS US EAST"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"connections": {
|
||||||
|
"When clicking ‘Test workflow’": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "AWS IAM12",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pinData": {
|
||||||
|
"AWS IAM12": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"UpdateUserResponse": {
|
||||||
|
"ResponseMetadata": {
|
||||||
|
"RequestId": "bdb4a8b5-627a-41a7-aba9-5733b7869c16"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
54
packages/nodes-base/nodes/Aws/IAM/transport/index.ts
Normal file
54
packages/nodes-base/nodes/Aws/IAM/transport/index.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import type {
|
||||||
|
IExecuteSingleFunctions,
|
||||||
|
IDataObject,
|
||||||
|
IHttpRequestOptions,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
IPollFunctions,
|
||||||
|
JsonObject,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { NodeApiError } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { BASE_URL } from '../helpers/constants';
|
||||||
|
|
||||||
|
const errorMapping: IDataObject = {
|
||||||
|
403: 'The AWS credentials are not valid!',
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function awsApiRequest(
|
||||||
|
this: ILoadOptionsFunctions | IPollFunctions | IExecuteSingleFunctions,
|
||||||
|
opts: IHttpRequestOptions,
|
||||||
|
): Promise<IDataObject> {
|
||||||
|
const requestOptions: IHttpRequestOptions = {
|
||||||
|
baseURL: BASE_URL,
|
||||||
|
json: true,
|
||||||
|
...opts,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
...(opts.headers ?? {}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (opts.body) {
|
||||||
|
requestOptions.body = new URLSearchParams(opts.body as Record<string, string>).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = (await this.helpers.requestWithAuthentication.call(
|
||||||
|
this,
|
||||||
|
'aws',
|
||||||
|
requestOptions,
|
||||||
|
)) as IDataObject;
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
const statusCode = (error?.statusCode || error?.cause?.statusCode) as string;
|
||||||
|
|
||||||
|
if (statusCode && errorMapping[statusCode]) {
|
||||||
|
throw new NodeApiError(this.getNode(), {
|
||||||
|
message: `AWS error response [${statusCode}]: ${errorMapping[statusCode] as string}`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new NodeApiError(this.getNode(), error as JsonObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -435,6 +435,7 @@
|
|||||||
"dist/nodes/Aws/Comprehend/AwsComprehend.node.js",
|
"dist/nodes/Aws/Comprehend/AwsComprehend.node.js",
|
||||||
"dist/nodes/Aws/DynamoDB/AwsDynamoDB.node.js",
|
"dist/nodes/Aws/DynamoDB/AwsDynamoDB.node.js",
|
||||||
"dist/nodes/Aws/ELB/AwsElb.node.js",
|
"dist/nodes/Aws/ELB/AwsElb.node.js",
|
||||||
|
"dist/nodes/Aws/IAM/AwsIam.node.js",
|
||||||
"dist/nodes/Aws/Rekognition/AwsRekognition.node.js",
|
"dist/nodes/Aws/Rekognition/AwsRekognition.node.js",
|
||||||
"dist/nodes/Aws/S3/AwsS3.node.js",
|
"dist/nodes/Aws/S3/AwsS3.node.js",
|
||||||
"dist/nodes/Aws/SES/AwsSes.node.js",
|
"dist/nodes/Aws/SES/AwsSes.node.js",
|
||||||
|
|||||||
Reference in New Issue
Block a user