Skip to content

Commit 4a0e4c8

Browse files
Added support for tags for IAM users
Signed-off-by: Aayush Chouhan <achouhan@redhat.com>
1 parent 263ca63 commit 4a0e4c8

File tree

11 files changed

+426
-13
lines changed

11 files changed

+426
-13
lines changed

src/endpoint/iam/iam_constants.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ const IAM_ACTIONS = Object.freeze({
1212
GET_ACCESS_KEY_LAST_USED: 'get_access_key_last_used',
1313
UPDATE_ACCESS_KEY: 'update_access_key',
1414
DELETE_ACCESS_KEY: 'delete_access_key',
15-
LIST_ACCESS_KEYS: 'list_access_keys'
15+
LIST_ACCESS_KEYS: 'list_access_keys',
16+
TAG_USER: 'tag_user',
17+
UNTAG_USER: 'untag_user',
18+
LIST_USER_TAGS: 'list_user_tags',
1619
});
1720

1821
// key: action - the function name on accountspace_fs (snake case style)
@@ -29,6 +32,9 @@ const ACTION_MESSAGE_TITLE_MAP = Object.freeze({
2932
'update_access_key': 'UpdateAccessKey',
3033
'delete_access_key': 'DeleteAccessKey',
3134
'list_access_keys': 'ListAccessKeys',
35+
'tag_user': 'TagUser',
36+
'untag_user': 'UntagUser',
37+
'list_user_tags': 'ListUserTags',
3238
});
3339

3440
const ACCESS_KEY_STATUS_ENUM = Object.freeze({
@@ -43,6 +49,7 @@ const IDENTITY_ENUM = Object.freeze({
4349

4450
// miscellaneous
4551
const DEFAULT_MAX_ITEMS = 100;
52+
const DEFAULT_MAX_TAGS = 50;
4653
const MAX_NUMBER_OF_ACCESS_KEYS = 2;
4754
const IAM_DEFAULT_PATH = '/';
4855
const AWS_NOT_USED = 'N/A'; // can be used in case the region or the service name were not used
@@ -66,6 +73,7 @@ exports.ACTION_MESSAGE_TITLE_MAP = ACTION_MESSAGE_TITLE_MAP;
6673
exports.ACCESS_KEY_STATUS_ENUM = ACCESS_KEY_STATUS_ENUM;
6774
exports.IDENTITY_ENUM = IDENTITY_ENUM;
6875
exports.DEFAULT_MAX_ITEMS = DEFAULT_MAX_ITEMS;
76+
exports.DEFAULT_MAX_TAGS = DEFAULT_MAX_TAGS;
6977
exports.MAX_NUMBER_OF_ACCESS_KEYS = MAX_NUMBER_OF_ACCESS_KEYS;
7078
exports.IAM_DEFAULT_PATH = IAM_DEFAULT_PATH;
7179
exports.AWS_NOT_USED = AWS_NOT_USED;

src/endpoint/iam/iam_rest.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ const ACTIONS = Object.freeze({
6363
'ListUserPolicies': 'list_user_policies',
6464
'ListUserTags': 'list_user_tags',
6565
'ListVirtualMFADevices': 'list_virtual_mfa_devices',
66+
'TagUser': 'tag_user',
67+
'UntagUser': 'untag_user',
6668
});
6769

6870
// notice: shows all methods as method post
@@ -79,6 +81,10 @@ const IAM_OPS = js_utils.deep_freeze({
7981
post_update_access_key: require('./ops/iam_update_access_key'),
8082
post_delete_access_key: require('./ops/iam_delete_access_key'),
8183
post_list_access_keys: require('./ops/iam_list_access_keys'),
84+
// user tags
85+
post_tag_user: require('./ops/iam_tag_user'),
86+
post_untag_user: require('./ops/iam_untag_user'),
87+
post_list_user_tags: require('./ops/iam_list_user_tags'),
8288
// other (currently ops that return empty or NoSuchEntity error - just not to fail them)
8389
post_list_groups_for_user: require('./ops/iam_list_groups_for_user.js'),
8490
post_list_account_aliases: require('./ops/iam_list_account_aliases.js'),
@@ -107,7 +113,6 @@ const IAM_OPS = js_utils.deep_freeze({
107113
post_list_signing_certificates: require('./ops/iam_list_signing_certificates.js'),
108114
post_list_ssh_public_keys: require('./ops/iam_list_ssh_public_keys.js'),
109115
post_list_user_policies: require('./ops/iam_list_user_policies.js'),
110-
post_list_user_tags: require('./ops/iam_list_user_tags.js'),
111116
post_list_virtual_mfa_devices: require('./ops/iam_list_virtual_mfa_devices.js'),
112117
});
113118

src/endpoint/iam/iam_utils.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,15 @@ function validate_user_params(action, params) {
117117
case iam_constants.IAM_ACTIONS.LIST_USERS:
118118
validate_list_users(params);
119119
break;
120+
case iam_constants.IAM_ACTIONS.TAG_USER:
121+
validate_tag_user_params(params);
122+
break;
123+
case iam_constants.IAM_ACTIONS.UNTAG_USER:
124+
validate_untag_user_params(params);
125+
break;
126+
case iam_constants.IAM_ACTIONS.LIST_USER_TAGS:
127+
validate_list_user_tags_params(params);
128+
break;
120129
default:
121130
throw new RpcError('INTERNAL_ERROR', `${action} is not supported`);
122131
}
@@ -478,6 +487,49 @@ function translate_rpc_error(err) {
478487
throw err;
479488
}
480489

490+
/**
491+
* validate_tag_user_params checks the params for tag_user action
492+
* @param {object} params
493+
*/
494+
function validate_tag_user_params(params) {
495+
try {
496+
check_required_username(params);
497+
validation_utils.validate_iam_tags(params.tags);
498+
validation_utils.validate_username(params.username, iam_constants.IAM_PARAMETER_NAME.USERNAME);
499+
} catch (err) {
500+
translate_rpc_error(err);
501+
}
502+
}
503+
504+
/**
505+
* validate_untag_user_params checks the params for untag_user action
506+
* @param {object} params
507+
*/
508+
function validate_untag_user_params(params) {
509+
try {
510+
check_required_username(params);
511+
validation_utils.validate_iam_tag_keys(params.tag_keys);
512+
validation_utils.validate_username(params.username, iam_constants.IAM_PARAMETER_NAME.USERNAME);
513+
} catch (err) {
514+
translate_rpc_error(err);
515+
}
516+
}
517+
518+
/**
519+
* validate_list_user_tags_params checks the params for list_user_tags action
520+
* @param {object} params
521+
*/
522+
function validate_list_user_tags_params(params) {
523+
try {
524+
check_required_username(params);
525+
validate_marker(params.marker);
526+
validate_max_items(params.max_items);
527+
validation_utils.validate_username(params.username, iam_constants.IAM_PARAMETER_NAME.USERNAME);
528+
} catch (err) {
529+
translate_rpc_error(err);
530+
}
531+
}
532+
481533
// EXPORTS
482534
exports.format_iam_xml_date = format_iam_xml_date;
483535
exports.create_arn_for_user = create_arn_for_user;
@@ -490,4 +542,7 @@ exports.validate_iam_path = validate_iam_path;
490542
exports.validate_marker = validate_marker;
491543
exports.validate_access_key_id = validate_access_key_id;
492544
exports.validate_status = validate_status;
545+
exports.validate_tag_user_params = validate_tag_user_params;
546+
exports.validate_untag_user_params = validate_untag_user_params;
547+
exports.validate_list_user_tags_params = validate_list_user_tags_params;
493548

src/endpoint/iam/ops/iam_list_user_tags.js

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,26 @@ async function list_user_tags(req, res) {
1414
const params = {
1515
username: req.body.user_name,
1616
marker: req.body.marker,
17-
max_items: iam_utils.parse_max_items(req.body.max_items) ?? iam_constants.DEFAULT_MAX_ITEMS,
17+
max_items: iam_utils.parse_max_items(req.body.max_items) ?? iam_constants.DEFAULT_MAX_TAGS,
1818
};
1919

20-
dbg.log1('To check that we have the user we will run the IAM GET USER', params);
21-
iam_utils.validate_params(iam_constants.IAM_ACTIONS.GET_USER, params);
22-
await req.account_sdk.get_user(params);
20+
dbg.log1('IAM LIST USER TAGS', params);
21+
iam_utils.validate_params(iam_constants.IAM_ACTIONS.LIST_USER_TAGS, params);
22+
const reply = await req.account_sdk.list_user_tags(params);
2323

24-
dbg.log1('IAM LIST USER TAGS (returns empty list on every request)', params);
24+
const result = {
25+
Tags: reply.tags,
26+
IsTruncated: reply.is_truncated
27+
};
28+
29+
if (reply.marker) {
30+
result.Marker = reply.marker;
31+
}
32+
dbg.log2('list_user_tags reply', result);
2533

2634
return {
2735
ListUserTagsResponse: {
28-
ListUserTagsResult: {
29-
Tags: [],
30-
IsTruncated: false,
31-
},
36+
ListUserTagsResult: result,
3237
ResponseMetadata: {
3338
RequestId: req.request_id,
3439
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/* Copyright (C) 2024 NooBaa */
2+
'use strict';
3+
4+
const dbg = require('../../../util/debug_module')(__filename);
5+
const iam_utils = require('../iam_utils');
6+
const iam_constants = require('../iam_constants');
7+
const { CONTENT_TYPE_APP_FORM_URLENCODED } = require('../../../util/http_utils');
8+
9+
/**
10+
* https://docs.aws.amazon.com/IAM/latest/APIReference/API_TagUser.html
11+
*/
12+
async function tag_user(req, res) {
13+
14+
const tags = [];
15+
let tag_index = 1;
16+
while (req.body[`tags_member_${tag_index}_key`]) {
17+
tags.push({
18+
key: req.body[`tags_member_${tag_index}_key`],
19+
value: req.body[`tags_member_${tag_index}_value`] || ''
20+
});
21+
tag_index += 1;
22+
}
23+
const params = {
24+
username: req.body.user_name,
25+
tags: tags,
26+
};
27+
28+
dbg.log1('IAM TAG USER', params);
29+
iam_utils.validate_params(iam_constants.IAM_ACTIONS.TAG_USER, params);
30+
await req.account_sdk.tag_user(params);
31+
32+
return {
33+
TagUserResponse: {
34+
ResponseMetadata: {
35+
RequestId: req.request_id,
36+
}
37+
},
38+
};
39+
}
40+
41+
module.exports = {
42+
handler: tag_user,
43+
body: {
44+
type: CONTENT_TYPE_APP_FORM_URLENCODED,
45+
},
46+
reply: {
47+
type: 'xml',
48+
},
49+
};
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/* Copyright (C) 2024 NooBaa */
2+
'use strict';
3+
4+
const dbg = require('../../../util/debug_module')(__filename);
5+
const iam_utils = require('../iam_utils');
6+
const iam_constants = require('../iam_constants');
7+
const { CONTENT_TYPE_APP_FORM_URLENCODED } = require('../../../util/http_utils');
8+
9+
/**
10+
* https://docs.aws.amazon.com/IAM/latest/APIReference/API_UntagUser.html
11+
*/
12+
async function untag_user(req, res) {
13+
14+
const tag_keys = [];
15+
let tag_index = 1;
16+
while (req.body[`tag_keys_member_${tag_index}`]) {
17+
tag_keys.push(req.body[`tag_keys_member_${tag_index}`]);
18+
tag_index += 1;
19+
}
20+
const params = {
21+
username: req.body.user_name,
22+
tag_keys: tag_keys,
23+
};
24+
25+
dbg.log1('IAM UNTAG USER', params);
26+
iam_utils.validate_params(iam_constants.IAM_ACTIONS.UNTAG_USER, params);
27+
await req.account_sdk.untag_user(params);
28+
29+
return {
30+
UntagUserResponse: {
31+
ResponseMetadata: {
32+
RequestId: req.request_id,
33+
}
34+
},
35+
};
36+
}
37+
38+
module.exports = {
39+
handler: untag_user,
40+
body: {
41+
type: CONTENT_TYPE_APP_FORM_URLENCODED,
42+
},
43+
reply: {
44+
type: 'xml',
45+
},
46+
};

src/sdk/account_sdk.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,25 @@ class AccountSDK {
117117
return accountspace.list_users(params, this);
118118
}
119119

120+
////////////
121+
// TAGS //
122+
////////////
123+
124+
async tag_user(params) {
125+
const accountspace = this._get_accountspace();
126+
return accountspace.tag_user(params, this);
127+
}
128+
129+
async untag_user(params) {
130+
const accountspace = this._get_accountspace();
131+
return accountspace.untag_user(params, this);
132+
}
133+
134+
async list_user_tags(params) {
135+
const accountspace = this._get_accountspace();
136+
return accountspace.list_user_tags(params, this);
137+
}
138+
120139
////////////////
121140
// ACCESS KEY //
122141
////////////////

0 commit comments

Comments
 (0)