diff --git a/moto/iam/models.py b/moto/iam/models.py index 1e4b58578..e30ad09d4 100644 --- a/moto/iam/models.py +++ b/moto/iam/models.py @@ -76,6 +76,10 @@ class ManagedPolicy(Policy): self.attachment_count += 1 role.managed_policies[self.name] = self + def detach_from_role(self, role): + self.attachment_count -= 1 + del role.managed_policies[self.name] + class AWSManagedPolicy(ManagedPolicy): """AWS-managed policy.""" @@ -120,6 +124,13 @@ class Role(BaseModel): def put_policy(self, policy_name, policy_json): self.policies[policy_name] = policy_json + def delete_policy(self, policy_name): + try: + del self.policies[policy_name] + except KeyError: + raise IAMNotFoundException( + "The role policy with name {0} cannot be found.".format(policy_name)) + @property def physical_resource_id(self): return self.id @@ -497,6 +508,14 @@ class IAMBackend(BaseBackend): policy = arns[policy_arn] policy.attach_to_role(self.get_role(role_name)) + def detach_role_policy(self, policy_arn, role_name): + arns = dict((p.arn, p) for p in self.managed_policies.values()) + try: + policy = arns[policy_arn] + policy.detach_from_role(self.get_role(role_name)) + except KeyError: + raise IAMNotFoundException("Policy {0} was not found.".format(policy_arn)) + def create_policy(self, description, path, policy_document, policy_name): policy = ManagedPolicy( policy_name, @@ -584,6 +603,10 @@ class IAMBackend(BaseBackend): role = self.get_role(role_name) role.put_policy(policy_name, policy_json) + def delete_role_policy(self, role_name, policy_name): + role = self.get_role(role_name) + role.delete_policy(policy_name) + def get_role_policy(self, role_name, policy_name): role = self.get_role(role_name) for p, d in role.policies.items(): diff --git a/moto/iam/responses.py b/moto/iam/responses.py index a5e5081c3..5929a2005 100644 --- a/moto/iam/responses.py +++ b/moto/iam/responses.py @@ -13,6 +13,13 @@ class IamResponse(BaseResponse): template = self.response_template(ATTACH_ROLE_POLICY_TEMPLATE) return template.render() + def detach_role_policy(self): + role_name = self._get_param('RoleName') + policy_arn = self._get_param('PolicyArn') + iam_backend.detach_role_policy(policy_arn, role_name) + template = self.response_template(GENERIC_EMPTY_TEMPLATE) + return template.render(name="DetachRolePolicyResponse") + def create_policy(self): description = self._get_param('Description') path = self._get_param('Path') @@ -82,6 +89,13 @@ class IamResponse(BaseResponse): template = self.response_template(GENERIC_EMPTY_TEMPLATE) return template.render(name="PutRolePolicyResponse") + def delete_role_policy(self): + role_name = self._get_param('RoleName') + policy_name = self._get_param('PolicyName') + iam_backend.delete_role_policy(role_name, policy_name) + template = self.response_template(GENERIC_EMPTY_TEMPLATE) + return template.render(name="DeleteRolePolicyResponse") + def get_role_policy(self): role_name = self._get_param('RoleName') policy_name = self._get_param('PolicyName') @@ -446,6 +460,12 @@ ATTACH_ROLE_POLICY_TEMPLATE = """ """ +DETACH_ROLE_POLICY_TEMPLATE = """ + + 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE + +""" + CREATE_POLICY_TEMPLATE = """ diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py index b5968f722..335b458ea 100644 --- a/tests/test_iam/test_iam.py +++ b/tests/test_iam/test_iam.py @@ -213,8 +213,21 @@ def test_list_role_policies(): conn.create_role("my-role") conn.put_role_policy("my-role", "test policy", "my policy") role = conn.list_role_policies("my-role") + role.policy_names.should.have.length_of(1) role.policy_names[0].should.equal("test policy") + conn.put_role_policy("my-role", "test policy 2", "another policy") + role = conn.list_role_policies("my-role") + role.policy_names.should.have.length_of(2) + + conn.delete_role_policy("my-role", "test policy") + role = conn.list_role_policies("my-role") + role.policy_names.should.have.length_of(1) + role.policy_names[0].should.equal("test policy 2") + + with assert_raises(BotoServerError): + conn.delete_role_policy("my-role", "test policy") + @mock_iam_deprecated() def test_put_role_policy(): @@ -548,6 +561,31 @@ def test_managed_policy(): resp['list_attached_role_policies_response']['list_attached_role_policies_result'][ 'attached_policies'].should.have.length_of(2) + conn.detach_role_policy( + "arn:aws:iam::aws:policy/service-role/AmazonElasticMapReduceRole", + role_name) + rows = conn.list_policies(only_attached=True)['list_policies_response'][ + 'list_policies_result']['policies'] + rows.should.have.length_of(1) + for x in rows: + int(x['attachment_count']).should.be.greater_than(0) + + # boto has not implemented this end point but accessible this way + resp = conn.get_response('ListAttachedRolePolicies', + {'RoleName': role_name}, + list_marker='AttachedPolicies') + resp['list_attached_role_policies_response']['list_attached_role_policies_result'][ + 'attached_policies'].should.have.length_of(1) + + with assert_raises(BotoServerError): + conn.detach_role_policy( + "arn:aws:iam::aws:policy/service-role/AmazonElasticMapReduceRole", + role_name) + + with assert_raises(BotoServerError): + conn.detach_role_policy( + "arn:aws:iam::aws:policy/Nonexistent", role_name) + @mock_iam def test_boto3_create_login_profile():