Skip to content

Commit 2017b80

Browse files
authored
Merge pull request #9270 from shirady/iam-user-policy-implementations
IAM | User Inline Policy Implementation
2 parents 0d75c6a + 914f545 commit 2017b80

File tree

6 files changed

+163
-84
lines changed

6 files changed

+163
-84
lines changed

src/endpoint/iam/iam_constants.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ const IAM_ACTIONS = Object.freeze({
2222
LIST_USER_POLICIES: 'list_user_policies',
2323
});
2424

25+
const IAM_ACTIONS_USER_INLINE_POLICY = [
26+
IAM_ACTIONS.PUT_USER_POLICY,
27+
IAM_ACTIONS.GET_USER_POLICY,
28+
IAM_ACTIONS.DELETE_USER_POLICY,
29+
IAM_ACTIONS.LIST_USER_POLICIES
30+
];
31+
2532
// key: action - the function name on accountspace_fs (snake case style)
2633
// value: AWS action name (camel case style)
2734
// we use it for error message to match AWS style
@@ -62,6 +69,7 @@ const MAX_NUMBER_OF_ACCESS_KEYS = 2;
6269
const IAM_DEFAULT_PATH = '/';
6370
const AWS_NOT_USED = 'N/A'; // can be used in case the region or the service name were not used
6471
const IAM_SERVICE_SMALL_LETTERS = 'iam';
72+
const AWS_LIMIT_CHARS_USER_INlINE_POLICY = 2048;
6573

6674
// parameter names in camel case style
6775
// we will use in the error messages
@@ -79,12 +87,14 @@ const IAM_SPLIT_CHARACTERS = ':';
7987

8088
// EXPORTS
8189
exports.IAM_ACTIONS = IAM_ACTIONS;
90+
exports.IAM_ACTIONS_USER_INLINE_POLICY = IAM_ACTIONS_USER_INLINE_POLICY;
8291
exports.ACTION_MESSAGE_TITLE_MAP = ACTION_MESSAGE_TITLE_MAP;
8392
exports.ACCESS_KEY_STATUS_ENUM = ACCESS_KEY_STATUS_ENUM;
8493
exports.IDENTITY_ENUM = IDENTITY_ENUM;
8594
exports.DEFAULT_MAX_ITEMS = DEFAULT_MAX_ITEMS;
8695
exports.MAX_TAGS = MAX_TAGS;
8796
exports.MAX_NUMBER_OF_ACCESS_KEYS = MAX_NUMBER_OF_ACCESS_KEYS;
97+
exports.AWS_LIMIT_CHARS_USER_INlINE_POLICY = AWS_LIMIT_CHARS_USER_INlINE_POLICY;
8898
exports.IAM_DEFAULT_PATH = IAM_DEFAULT_PATH;
8999
exports.AWS_NOT_USED = AWS_NOT_USED;
90100
exports.IAM_SERVICE_SMALL_LETTERS = IAM_SERVICE_SMALL_LETTERS;

src/endpoint/iam/ops/iam_list_user_policies.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ async function list_user_policies(req, res) {
2525
return {
2626
ListUserPoliciesResponse: {
2727
ListUserPoliciesResult: {
28-
PolicyNames: reply.members,
28+
PolicyNames: reply.members.map(member => ({
29+
member: member,
30+
})),
2931
IsTruncated: reply.is_truncated,
3032
},
3133
ResponseMetadata: {

src/sdk/accountspace_fs.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -866,8 +866,9 @@ class AccountSpaceFS {
866866

867867
_check_if_user_does_not_have_access_keys_before_deletion(action, account_to_delete) {
868868
const resource_name = 'access keys';
869-
const is_access_keys_removed = account_to_delete.access_keys.length === 0;
870-
if (!is_access_keys_removed) {
869+
const access_keys = account_to_delete.access_keys || [];
870+
const is_access_keys_empty = access_keys.length === 0;
871+
if (!is_access_keys_empty) {
871872
this._throw_error_delete_conflict(action, account_to_delete, resource_name);
872873
}
873874
}

src/sdk/accountspace_nb.js

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ class AccountSpaceNB {
162162
const requested_account = system_store.get_account_by_email(username);
163163
account_util._check_if_requested_account_is_root_account_or_IAM_user(action, requesting_account, requested_account);
164164
account_util._check_if_requested_is_owned_by_root_account(action, requesting_account, requested_account);
165+
account_util._check_if_user_does_not_have_resources_before_deletion(action, requested_account);
165166
// TODO: DELETE INLINE POLICY : Manually
166167
// TODO: DELETE ACCESS KEY : manually
167168
const req = {
@@ -440,27 +441,79 @@ class AccountSpaceNB {
440441
////////////////////
441442

442443
async put_user_policy(params, account_sdk) {
443-
dbg.log0('AccountSpaceNB.put_user_policy:', params);
444-
const { code, http_code, type } = IamError.NotImplemented;
445-
throw new IamError({ code, message: 'NotImplemented', http_code, type });
444+
const action = IAM_ACTIONS.PUT_USER_POLICY;
445+
dbg.log1(`AccountSpaceNB.${action}`, params);
446+
const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email);
447+
const requested_account = validate_and_return_requested_account(params, action, requesting_account, account_sdk);
448+
const iam_user_policies = requested_account.iam_user_policies || [];
449+
const index_of_iam_user_policy = account_util._get_iam_user_policy_index(iam_user_policies, params.policy_name);
450+
const iam_user_policy_to_add = {
451+
policy_name: params.policy_name,
452+
policy_document: params.policy_document,
453+
};
454+
if (index_of_iam_user_policy === -1) {
455+
iam_user_policies.push(iam_user_policy_to_add);
456+
} else {
457+
iam_user_policies[index_of_iam_user_policy] = iam_user_policy_to_add;
458+
}
459+
460+
account_util._check_total_policy_size(iam_user_policies, params.username);
461+
462+
await system_store.make_changes({
463+
update: {
464+
accounts: [{
465+
_id: requested_account._id,
466+
$set: { iam_user_policies },
467+
}]
468+
}
469+
});
446470
}
447471

448472
async get_user_policy(params, account_sdk) {
449-
dbg.log0('AccountSpaceNB.get_user_policy:', params);
450-
const { code, http_code, type } = IamError.NotImplemented;
451-
throw new IamError({ code, message: 'NotImplemented', http_code, type });
473+
const action = IAM_ACTIONS.GET_USER_POLICY;
474+
dbg.log1(`AccountSpaceNB.${action}`, params);
475+
const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email);
476+
const requested_account = validate_and_return_requested_account(params, action, requesting_account, account_sdk);
477+
const iam_user_policies = requested_account.iam_user_policies || [];
478+
const iam_user_policy_index = account_util._check_user_policy_exists(action, iam_user_policies, params.policy_name);
479+
return {
480+
username: params.username,
481+
policy_name: params.policy_name,
482+
policy_document: JSON.stringify(iam_user_policies[iam_user_policy_index].policy_document),
483+
};
452484
}
453485

454486
async delete_user_policy(params, account_sdk) {
455-
dbg.log0('AccountSpaceNB.delete_user_policy:', params);
456-
const { code, http_code, type } = IamError.NotImplemented;
457-
throw new IamError({ code, message: 'NotImplemented', http_code, type });
487+
const action = IAM_ACTIONS.DELETE_USER_POLICY;
488+
dbg.log1(`AccountSpaceNB.${action}`, params);
489+
const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email);
490+
const requested_account = validate_and_return_requested_account(params, action, requesting_account, account_sdk);
491+
const iam_user_policies = requested_account.iam_user_policies || [];
492+
const iam_user_policy_index = account_util._check_user_policy_exists(action, iam_user_policies, params.policy_name);
493+
iam_user_policies.splice(iam_user_policy_index, 1);
494+
495+
await system_store.make_changes({
496+
update: {
497+
accounts: [{
498+
_id: requested_account._id,
499+
$set: { iam_user_policies },
500+
}]
501+
}
502+
});
458503
}
459504

460505
async list_user_policies(params, account_sdk) {
461-
dbg.log0('AccountSpaceNB.list_user_policies:', params);
462-
const { code, http_code, type } = IamError.NotImplemented;
463-
throw new IamError({ code, message: 'NotImplemented', http_code, type });
506+
const action = IAM_ACTIONS.LIST_USER_POLICIES;
507+
dbg.log1(`AccountSpaceNB.${action}`, params);
508+
const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email);
509+
const requested_account = validate_and_return_requested_account(params, action, requesting_account, account_sdk);
510+
const is_truncated = false; // GAP - no pagination at this point
511+
let members = _.map(requested_account.iam_user_policies || [], iam_user_policy => iam_user_policy.policy_name);
512+
members = members.sort((a, b) => a.localeCompare(b));
513+
return {
514+
is_truncated,
515+
members
516+
};
464517
}
465518
}
466519

@@ -473,10 +526,10 @@ function validate_and_return_requested_account(params, action, requesting_accoun
473526
// So in that case requesting account and requested account is same.
474527
requested_account = requesting_account;
475528
} else {
529+
account_util._check_if_requesting_account_is_root_account(action, requesting_account, { username: params.username });
476530
const account_email = account_util.get_account_name_from_username(params.username, requesting_account.name.unwrap());
477531
account_util._check_if_account_exists(action, account_email);
478532
requested_account = system_store.get_account_by_email(account_email);
479-
account_util._check_if_requesting_account_is_root_account(action, requesting_account, { username: params.username });
480533
account_util._check_if_requested_account_is_root_account_or_IAM_user(action, requesting_account, requested_account);
481534
account_util._check_if_requested_is_owned_by_root_account(action, requesting_account, requested_account);
482535
}

src/test/unit_tests/internal/test_accountspace_nb.test.js

Lines changed: 0 additions & 64 deletions
This file was deleted.

src/util/account_util.js

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ const { OP_NAME_TO_ACTION } = require('../endpoint/sts/sts_rest');
1515
const IamError = require('../endpoint/iam/iam_errors').IamError;
1616
const { create_arn_for_user, get_action_message_title } = require('../endpoint/iam/iam_utils');
1717
const { IAM_ACTIONS, MAX_NUMBER_OF_ACCESS_KEYS, IAM_DEFAULT_PATH,
18-
ACCESS_KEY_STATUS_ENUM, IAM_SPLIT_CHARACTERS } = require('../endpoint/iam/iam_constants');
18+
ACCESS_KEY_STATUS_ENUM, IAM_SPLIT_CHARACTERS, IAM_ACTIONS_USER_INLINE_POLICY,
19+
AWS_LIMIT_CHARS_USER_INlINE_POLICY } = require('../endpoint/iam/iam_constants');
1920

2021
const demo_access_keys = Object.freeze({
2122
access_key: new SensitiveString('123'),
@@ -360,8 +361,7 @@ function _check_if_requesting_account_is_root_account(action, requesting_account
360361
dbg.log1(`AccountSpaceNB.${action} requesting_account ID: ${requesting_account._id}` +
361362
`name: ${requesting_account.name.unwrap()}`, 'is_root_account', is_root_account);
362363
if (!is_root_account) {
363-
dbg.error(`AccountSpaceNB.${action} requesting account is not a root account`,
364-
requesting_account);
364+
dbg.error(`AccountSpaceNB.${action} requesting account is not a root account`, requesting_account._id);
365365
_throw_access_denied_error(action, requesting_account, user_details, "USER");
366366
}
367367
}
@@ -449,6 +449,13 @@ function _throw_error_no_such_entity_access_key(action, access_key_id) {
449449
throw new IamError({ code, message: message_with_details, http_code, type });
450450
}
451451

452+
function _throw_error_no_such_entity_policy(action, policy_name) {
453+
dbg.error(`AccountSpaceNB.${action} The user policy with name does not exist`, policy_name);
454+
const message_with_details = `The user policy with name ${policy_name} cannot be found`;
455+
const { code, http_code, type } = IamError.NoSuchEntity;
456+
throw new IamError({ code, message: message_with_details, http_code, type });
457+
}
458+
452459
function _throw_access_denied_error(action, requesting_account, details, entity) {
453460
const full_action_name = get_action_message_title(action);
454461
const account_id_for_arn = _get_account_owner_id_for_arn(requesting_account);
@@ -459,7 +466,8 @@ function _throw_access_denied_error(action, requesting_account, details, entity)
459466
let message_with_details;
460467
if (entity === 'USER') {
461468
let user_message;
462-
if (action === IAM_ACTIONS.LIST_ACCESS_KEYS) {
469+
if (action === IAM_ACTIONS.LIST_ACCESS_KEYS ||
470+
IAM_ACTIONS_USER_INLINE_POLICY.includes(action)) {
463471
user_message = `user ${details.username}`;
464472
} else {
465473
user_message = create_arn_for_user(account_id_for_arn, details.username, details.path);
@@ -473,6 +481,14 @@ function _throw_access_denied_error(action, requesting_account, details, entity)
473481
throw new IamError({ code, message: message_with_details, http_code, type });
474482
}
475483

484+
function _throw_error_delete_conflict(action, account_to_delete, resource_name) {
485+
dbg.error(`AccountSpaceNB.${action} requested account ` +
486+
`${account_to_delete.name} ${account_to_delete._id} has ${resource_name}`);
487+
const message_with_details = `Cannot delete entity, must delete ${resource_name} first.`;
488+
const { code, http_code, type } = IamError.DeleteConflict;
489+
throw new IamError({ code, message: message_with_details, http_code, type });
490+
}
491+
476492
// ACCESS KEY VALIDATIONS
477493

478494
function _check_number_of_access_key_array(action, requested_account) {
@@ -569,6 +585,63 @@ function _list_access_keys_from_account(requesting_account, account, on_itself)
569585
return members;
570586
}
571587

588+
function _check_user_policy_exists(action, iam_user_policies, policy_name) {
589+
const iam_user_policy_index = _get_iam_user_policy_index(iam_user_policies, policy_name);
590+
if (iam_user_policy_index === -1) {
591+
_throw_error_no_such_entity_policy(action, policy_name);
592+
}
593+
return iam_user_policy_index;
594+
}
595+
596+
function _get_iam_user_policy_index(iam_user_policies, policy_name) {
597+
const iam_user_policy_index = iam_user_policies.findIndex(current_iam_user_policy =>
598+
current_iam_user_policy.policy_name === policy_name);
599+
return iam_user_policy_index;
600+
}
601+
602+
function _check_total_policy_size(iam_user_policies, username) {
603+
const total_chars_size = _get_total_size_of_policies(iam_user_policies);
604+
if (total_chars_size > AWS_LIMIT_CHARS_USER_INlINE_POLICY) {
605+
const message_with_details = `Maximum policy size of 2048 bytes exceeded for user ${username}`;
606+
const { code, http_code, type } = IamError.LimitExceeded;
607+
throw new IamError({ code, message: message_with_details, http_code, type });
608+
}
609+
}
610+
611+
// each char is byte and not including whitespaces
612+
function _get_total_size_of_policies(iam_user_policies) {
613+
let total_size = 0;
614+
for (const iam_user_policy of iam_user_policies) {
615+
const policy_as_string = JSON.stringify(iam_user_policy);
616+
total_size += policy_as_string.length;
617+
}
618+
return total_size;
619+
}
620+
621+
// only resources of IAM (without the case of root account)
622+
function _check_if_user_does_not_have_resources_before_deletion(action, account_to_delete) {
623+
_check_if_user_does_not_have_access_keys_before_deletion(action, account_to_delete);
624+
_check_if_user_does_not_have_user_policy_before_deletion(action, account_to_delete);
625+
}
626+
627+
function _check_if_user_does_not_have_access_keys_before_deletion(action, account_to_delete) {
628+
const resource_name = 'access keys';
629+
const access_keys = account_to_delete.access_keys || [];
630+
const is_access_keys_empty = access_keys.length === 0;
631+
if (!is_access_keys_empty) {
632+
_throw_error_delete_conflict(action, account_to_delete, resource_name);
633+
}
634+
}
635+
636+
function _check_if_user_does_not_have_user_policy_before_deletion(action, account_to_delete) {
637+
const resource_name = 'policies';
638+
const iam_user_policies = account_to_delete.iam_user_policies || [];
639+
const is_policies_removed = iam_user_policies.length === 0;
640+
if (!is_policies_removed) {
641+
_throw_error_delete_conflict(action, account_to_delete, resource_name);
642+
}
643+
}
644+
572645
function validate_create_account_permissions(req) {
573646
const account = req.account;
574647
//For new system creation, nothing to be checked
@@ -665,3 +738,7 @@ exports._check_if_account_exists = _check_if_account_exists;
665738
exports._returned_username = _returned_username;
666739
exports._check_if_requested_is_owned_by_root_account = _check_if_requested_is_owned_by_root_account;
667740
exports._check_if_requested_account_is_root_account_or_IAM_user = _check_if_requested_account_is_root_account_or_IAM_user;
741+
exports._get_iam_user_policy_index = _get_iam_user_policy_index;
742+
exports._check_user_policy_exists = _check_user_policy_exists;
743+
exports._check_if_user_does_not_have_resources_before_deletion = _check_if_user_does_not_have_resources_before_deletion;
744+
exports._check_total_policy_size = _check_total_policy_size;

0 commit comments

Comments
 (0)