From 0a938f7bb4864b39464115559c120c57fb3add8e Mon Sep 17 00:00:00 2001 From: waynemetcalfe Date: Mon, 12 Oct 2020 12:13:20 +0100 Subject: [PATCH] issue-3379 iam list_roles: implement PathPrefix, MaxItems and Marker (#3380) * issue-3379 iam list_roles: implement PathPrefix, MaxItems and Marker * issue-3379 fix cloudformation test --- moto/iam/models.py | 25 +++--- moto/iam/responses.py | 17 ++-- .../test_cloudformation_stack_integration.py | 4 +- tests/test_iam/test_iam.py | 78 ++++++++++++++++++- 4 files changed, 106 insertions(+), 18 deletions(-) diff --git a/moto/iam/models.py b/moto/iam/models.py index 6397fd099..76b824d60 100755 --- a/moto/iam/models.py +++ b/moto/iam/models.py @@ -2011,16 +2011,23 @@ class IAMBackend(BaseBackend): user.name = new_user_name self.users[new_user_name] = self.users.pop(user_name) - def list_roles(self, path_prefix, marker, max_items): - roles = None - try: - roles = self.roles.values() - except KeyError: - raise IAMNotFoundException( - "Users {0}, {1}, {2} not found".format(path_prefix, marker, max_items) - ) + def list_roles(self, path_prefix=None, marker=None, max_items=None): + path_prefix = path_prefix if path_prefix else "/" + max_items = int(max_items) if max_items else 100 + start_index = int(marker) if marker else 0 - return roles + roles = self.roles.values() + roles = filter_items_with_path_prefix(path_prefix, roles) + sorted_roles = sorted(roles, key=lambda role: role.id) + + roles_to_return = sorted_roles[start_index : start_index + max_items] + + if len(sorted_roles) <= (start_index + max_items): + marker = None + else: + marker = str(start_index + max_items) + + return roles_to_return, marker def upload_signing_certificate(self, user_name, body): user = self.get_user(user_name) diff --git a/moto/iam/responses.py b/moto/iam/responses.py index eed610f13..55a7c2076 100644 --- a/moto/iam/responses.py +++ b/moto/iam/responses.py @@ -133,7 +133,7 @@ class IamResponse(BaseResponse): entity_users.append(user.name) elif entity == "Role": - roles = iam_backend.list_roles(path_prefix, marker, max_items) + roles, _ = iam_backend.list_roles(path_prefix, marker, max_items) if roles: for role in roles: for p in role.managed_policies: @@ -156,7 +156,7 @@ class IamResponse(BaseResponse): if p == policy_arn: entity_users.append(user.name) - roles = iam_backend.list_roles(path_prefix, marker, max_items) + roles, _ = iam_backend.list_roles(path_prefix, marker, max_items) if roles: for role in roles: for p in role.managed_policies: @@ -356,9 +356,13 @@ class IamResponse(BaseResponse): return template.render() def list_roles(self): - roles = iam_backend.get_roles() + path_prefix = self._get_param("PathPrefix", "/") + marker = self._get_param("Marker", "0") + max_items = self._get_param("MaxItems", 100) + + roles, marker = iam_backend.list_roles(path_prefix, marker, max_items) template = self.response_template(LIST_ROLES_TEMPLATE) - return template.render(roles=roles) + return template.render(roles=roles, marker=marker) def list_instance_profiles(self): profiles = iam_backend.get_instance_profiles() @@ -1379,7 +1383,10 @@ REMOVE_ROLE_FROM_INSTANCE_PROFILE_TEMPLATE = """ - false + {{ 'true' if marker else 'false' }} + {% if marker %} + {{ marker }} + {% endif %} {% for role in roles %} diff --git a/tests/test_cloudformation/test_cloudformation_stack_integration.py b/tests/test_cloudformation/test_cloudformation_stack_integration.py index ee2fbc94c..9949bb4a5 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_integration.py +++ b/tests/test_cloudformation/test_cloudformation_stack_integration.py @@ -870,7 +870,7 @@ def test_iam_roles(): } ] }, - "Path": "my-path", + "Path": "/my-path/", "Policies": [ { "PolicyDocument": { @@ -939,7 +939,7 @@ def test_iam_roles(): # Role name is not specified, so randomly generated - can't check exact name if "with-path" in role.role_name: role_name_to_id["with-path"] = role.role_id - role.path.should.equal("my-path") + role.path.should.equal("/my-path/") else: role_name_to_id["no-path"] = role.role_id role.role_name.should.equal("my-role-no-path-name") diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py index 9cf7decb6..7db2f0162 100644 --- a/tests/test_iam/test_iam.py +++ b/tests/test_iam/test_iam.py @@ -155,13 +155,13 @@ def test_create_role_and_instance_profile(): conn = boto.connect_iam() conn.create_instance_profile("my-profile", path="my-path") conn.create_role( - "my-role", assume_role_policy_document="some policy", path="my-path" + "my-role", assume_role_policy_document="some policy", path="/my-path/" ) conn.add_role_to_instance_profile("my-profile", "my-role") role = conn.get_role("my-role") - role.path.should.equal("my-path") + role.path.should.equal("/my-path/") role.assume_role_policy_document.should.equal("some policy") profile = conn.get_instance_profile("my-profile") @@ -3933,3 +3933,77 @@ def test_policy_config_client(): )["BaseConfigurationItems"][0]["resourceName"] == policies[8]["name"] ) + + +@mock_iam() +def test_list_roles_with_more_than_100_roles_no_max_items_defaults_to_100(): + iam = boto3.client("iam", region_name="us-east-1") + for i in range(150): + iam.create_role( + RoleName="test_role_{}".format(i), AssumeRolePolicyDocument="some policy" + ) + response = iam.list_roles() + roles = response["Roles"] + + assert response["IsTruncated"] is True + assert len(roles) == 100 + + +@mock_iam() +def test_list_roles_max_item_and_marker_values_adhered(): + iam = boto3.client("iam", region_name="us-east-1") + for i in range(10): + iam.create_role( + RoleName="test_role_{}".format(i), AssumeRolePolicyDocument="some policy" + ) + response = iam.list_roles(MaxItems=2) + roles = response["Roles"] + + assert response["IsTruncated"] is True + assert len(roles) == 2 + + response = iam.list_roles(Marker=response["Marker"]) + roles = response["Roles"] + + assert response["IsTruncated"] is False + assert len(roles) == 8 + + +@mock_iam() +def test_list_roles_path_prefix_value_adhered(): + iam = boto3.client("iam", region_name="us-east-1") + iam.create_role( + RoleName="test_role_without_path", AssumeRolePolicyDocument="some policy" + ) + iam.create_role( + RoleName="test_role_with_path", + AssumeRolePolicyDocument="some policy", + Path="/TestPath/", + ) + + response = iam.list_roles(PathPrefix="/TestPath/") + roles = response["Roles"] + + assert len(roles) == 1 + assert roles[0]["RoleName"] == "test_role_with_path" + + +@mock_iam() +def test_list_roles_none_found_returns_empty_list(): + iam = boto3.client("iam", region_name="us-east-1") + + response = iam.list_roles() + roles = response["Roles"] + assert len(roles) == 0 + + response = iam.list_roles(PathPrefix="/TestPath") + roles = response["Roles"] + assert len(roles) == 0 + + response = iam.list_roles(Marker="10") + roles = response["Roles"] + assert len(roles) == 0 + + response = iam.list_roles(MaxItems=10) + roles = response["Roles"] + assert len(roles) == 0