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():