Repository: getmoto/moto

Issue:
IoT Delete Operation Doesn't validate target attache state
### Summary
When calling iot delete functions such as delete_certificate and delete_thing,
if delete target attached some resoure,delete function must return error.
But moto dosn't return error

### Reproduction code
```
import boto3
from moto import mock_iot
from nose.tools import raises

@raises(Exception)
# @mock_iot
def test_attache_validation():
  # Create AWS IoT Resources
  iot = boto3.client('iot')
  policy = """{
    "Version": "2012-10-17",
    "Statement":[
        {
            "Effect":"Allow",
            "Action":[
                "iot: *"
            ],
            "Resource":"*"
        }
      ]
    }
    """
  iot.create_policy(policyName='testpolicy', policyDocument=policy)
  cert = iot.create_keys_and_certificate(setAsActive=True)
  iot.attach_principal_policy(policyName='testpolicy', principal=cert['certificateArn'])
  iot.create_thing(thingName='test-thing-1')
  iot.attach_thing_principal(thingName='test-thing-1', principal=cert['certificateArn'])

  # Delete AWS IoT Resources
  # this code must raises CertificateStateException
  iot.delete_certificate(certificateId=cert['certificateId'])
```
When I execute this test code,test code returnes OK.
But, comment out `@mock_iot` statement and execute test code,test code returnes AssertionError

### moto and boto version
```
boto3 (1.7.68)
botocore (1.10.68)
moto (1.3.3, /Users/xxxxxxxxxxxxxxx)
```

IoT Delete Operation Doesn't validate target attache state
### Summary
When calling iot delete functions such as delete_certificate and delete_thing,
if delete target attached some resoure,delete function must return error.
But moto dosn't return error

### Reproduction code
```
import boto3
from moto import mock_iot
from nose.tools import raises

@raises(Exception)
# @mock_iot
def test_attache_validation():
  # Create AWS IoT Resources
  iot = boto3.client('iot')
  policy = """{
    "Version": "2012-10-17",
    "Statement":[
        {
            "Effect":"Allow",
            "Action":[
                "iot: *"
            ],
            "Resource":"*"
        }
      ]
    }
    """
  iot.create_policy(policyName='testpolicy', policyDocument=policy)
  cert = iot.create_keys_and_certificate(setAsActive=True)
  iot.attach_principal_policy(policyName='testpolicy', principal=cert['certificateArn'])
  iot.create_thing(thingName='test-thing-1')
  iot.attach_thing_principal(thingName='test-thing-1', principal=cert['certificateArn'])

  # Delete AWS IoT Resources
  # this code must raises CertificateStateException
  iot.delete_certificate(certificateId=cert['certificateId'])
```
When I execute this test code,test code returnes OK.
But, comment out `@mock_iot` statement and execute test code,test code returnes AssertionError

### moto and boto version
```
boto3 (1.7.68)
botocore (1.10.68)
moto (1.3.3, /Users/xxxxxxxxxxxxxxx)
```



Discussion:

Resolving patch:
diff --git a/moto/iot/exceptions.py b/moto/iot/exceptions.py
--- a/moto/iot/exceptions.py
+++ b/moto/iot/exceptions.py
@@ -31,3 +31,20 @@ def __init__(self, name):
             'VersionConflictException',
             'The version for thing %s does not match the expected version.' % name
         )
+
+
+class CertificateStateException(IoTClientError):
+    def __init__(self, msg, cert_id):
+        self.code = 406
+        super(CertificateStateException, self).__init__(
+            'CertificateStateException',
+            '%s Id: %s' % (msg, cert_id)
+        )
+
+
+class DeleteConflictException(IoTClientError):
+    def __init__(self, msg):
+        self.code = 409
+        super(DeleteConflictException, self).__init__(
+            'DeleteConflictException', msg
+        )
diff --git a/moto/iot/models.py b/moto/iot/models.py
--- a/moto/iot/models.py
+++ b/moto/iot/models.py
@@ -13,6 +13,8 @@
 
 from moto.core import BaseBackend, BaseModel
 from .exceptions import (
+    CertificateStateException,
+    DeleteConflictException,
     ResourceNotFoundException,
     InvalidRequestException,
     VersionConflictException
@@ -378,7 +380,25 @@ def create_keys_and_certificate(self, set_as_active):
         return certificate, key_pair
 
     def delete_certificate(self, certificate_id):
-        self.describe_certificate(certificate_id)
+        cert = self.describe_certificate(certificate_id)
+        if cert.status == 'ACTIVE':
+            raise CertificateStateException(
+                'Certificate must be deactivated (not ACTIVE) before deletion.', certificate_id)
+
+        certs = [k[0] for k, v in self.principal_things.items()
+                 if self._get_principal(k[0]).certificate_id == certificate_id]
+        if len(certs) > 0:
+            raise DeleteConflictException(
+                'Things must be detached before deletion (arn: %s)' % certs[0]
+            )
+
+        certs = [k[0] for k, v in self.principal_policies.items()
+                 if self._get_principal(k[0]).certificate_id == certificate_id]
+        if len(certs) > 0:
+            raise DeleteConflictException(
+                'Certificate policies must be detached before deletion (arn: %s)' % certs[0]
+            )
+
         del self.certificates[certificate_id]
 
     def describe_certificate(self, certificate_id):
@@ -411,6 +431,14 @@ def get_policy(self, policy_name):
         return policies[0]
 
     def delete_policy(self, policy_name):
+
+        policies = [k[1] for k, v in self.principal_policies.items() if k[1] == policy_name]
+        if len(policies) > 0:
+            raise DeleteConflictException(
+                'The policy cannot be deleted as the policy is attached to one or more principals (name=%s)'
+                % policy_name
+            )
+
         policy = self.get_policy(policy_name)
         del self.policies[policy.name]
 


Test patch:
diff --git a/tests/test_iot/test_iot.py b/tests/test_iot/test_iot.py
--- a/tests/test_iot/test_iot.py
+++ b/tests/test_iot/test_iot.py
@@ -5,6 +5,8 @@
 import boto3
 
 from moto import mock_iot
+from botocore.exceptions import ClientError
+from nose.tools import assert_raises
 
 
 @mock_iot
@@ -261,6 +263,96 @@ def test_certs():
     res.should.have.key('certificates').which.should.have.length_of(0)
 
 
+@mock_iot
+def test_delete_policy_validation():
+    doc = """{
+    "Version": "2012-10-17",
+    "Statement":[
+        {
+            "Effect":"Allow",
+            "Action":[
+                "iot: *"
+            ],
+            "Resource":"*"
+        }
+      ]
+    }
+    """
+    client = boto3.client('iot', region_name='ap-northeast-1')
+    cert = client.create_keys_and_certificate(setAsActive=True)
+    cert_arn = cert['certificateArn']
+    policy_name = 'my-policy'
+    client.create_policy(policyName=policy_name, policyDocument=doc)
+    client.attach_principal_policy(policyName=policy_name, principal=cert_arn)
+
+    with assert_raises(ClientError) as e:
+        client.delete_policy(policyName=policy_name)
+    e.exception.response['Error']['Message'].should.contain(
+        'The policy cannot be deleted as the policy is attached to one or more principals (name=%s)' % policy_name)
+    res = client.list_policies()
+    res.should.have.key('policies').which.should.have.length_of(1)
+
+    client.detach_principal_policy(policyName=policy_name, principal=cert_arn)
+    client.delete_policy(policyName=policy_name)
+    res = client.list_policies()
+    res.should.have.key('policies').which.should.have.length_of(0)
+
+
+@mock_iot
+def test_delete_certificate_validation():
+    doc = """{
+    "Version": "2012-10-17",
+    "Statement":[
+        {
+            "Effect":"Allow",
+            "Action":[
+                "iot: *"
+            ],
+            "Resource":"*"
+        }
+      ]
+    }
+    """
+    client = boto3.client('iot', region_name='ap-northeast-1')
+    cert = client.create_keys_and_certificate(setAsActive=True)
+    cert_id = cert['certificateId']
+    cert_arn = cert['certificateArn']
+    policy_name = 'my-policy'
+    thing_name = 'thing-1'
+    client.create_policy(policyName=policy_name, policyDocument=doc)
+    client.attach_principal_policy(policyName=policy_name, principal=cert_arn)
+    client.create_thing(thingName=thing_name)
+    client.attach_thing_principal(thingName=thing_name, principal=cert_arn)
+
+    with assert_raises(ClientError) as e:
+        client.delete_certificate(certificateId=cert_id)
+    e.exception.response['Error']['Message'].should.contain(
+        'Certificate must be deactivated (not ACTIVE) before deletion.')
+    res = client.list_certificates()
+    res.should.have.key('certificates').which.should.have.length_of(1)
+
+    client.update_certificate(certificateId=cert_id, newStatus='REVOKED')
+    with assert_raises(ClientError) as e:
+        client.delete_certificate(certificateId=cert_id)
+    e.exception.response['Error']['Message'].should.contain(
+        'Things must be detached before deletion (arn: %s)' % cert_arn)
+    res = client.list_certificates()
+    res.should.have.key('certificates').which.should.have.length_of(1)
+
+    client.detach_thing_principal(thingName=thing_name, principal=cert_arn)
+    with assert_raises(ClientError) as e:
+        client.delete_certificate(certificateId=cert_id)
+    e.exception.response['Error']['Message'].should.contain(
+        'Certificate policies must be detached before deletion (arn: %s)' % cert_arn)
+    res = client.list_certificates()
+    res.should.have.key('certificates').which.should.have.length_of(1)
+
+    client.detach_principal_policy(policyName=policy_name, principal=cert_arn)
+    client.delete_certificate(certificateId=cert_id)
+    res = client.list_certificates()
+    res.should.have.key('certificates').which.should.have.length_of(0)
+
+
 @mock_iot
 def test_certs_create_inactive():
     client = boto3.client('iot', region_name='ap-northeast-1')