Repository: getmoto/moto

Issue:
iam: get_policy returns NotImplementedError but shouldn't
Versions:

```
 pip freeze|grep oto
boto==2.48.0
boto3==1.5.12
botocore==1.8.26
moto==1.2.0
```

According to `IMPLEMENTATION_COVERAGE.md`, and also [the code](https://github.com/spulec/moto/blob/master/moto/iam/models.py#L257-L267),
`iam:get_policy()` should be implemented, but the following pytest unit test gives me a `NotImplementedError`.

```python
import logging
# import pytest
from moto import mock_iam
import boto3

# silence verbose output from boto3/moto -
# https://github.com/boto/boto3/issues/521#issuecomment-240728480
logging.getLogger('boto3').setLevel(logging.CRITICAL)
logging.getLogger('botocore').setLevel(logging.CRITICAL)
logging.getLogger('nose').setLevel(logging.CRITICAL)


@mock_iam
def test_foo():
    iam = boto3.client("iam")
    policy = """
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "economycloud",
                "Action": [
                    "s3:List*",
                    "s3:Get*",
                ],
                "Effect": "Allow",
                "Resource": [
                    "arn:aws:s3:::REPLC/*",
                    "arn:aws:s3:::REPLC"
                ]
            },
            {
                "Sid": "AllowListing",
                "Action": [
                    "s3:ListBucket"
                ],
                "Effect": "Allow",
                "Resource": [
                    "arn:aws:s3:::*"
                ]
            },
        ]
    }
        """
    res0 = iam.create_policy(PolicyName="foo", PolicyDocument=policy)
    arn = res0['Policy']['Arn']
    version = res0['Policy']['DefaultVersionId']
    res1 = iam.get_policy(PolicyArn=arn)
    res2 = iam.get_policy_version(PolicyArn=arn, VersionId=version)

```

This fails (output below). Also, if I comment out the call to `get_policy()` the call to `get_policy_version()` fails, saying that there is no such version, but I'm using the version ID I got as a return value from `create_policy()`.

Output:

```
$ pytest footest.py
============================= test session starts ==============================
platform darwin -- Python 3.6.3, pytest-3.3.2, py-1.5.2, pluggy-0.6.0
rootdir: /Users/dtenenba/dev/economy-cloud, inifile:
collected 1 item

footest.py F                                                             [100%]

=================================== FAILURES ===================================
___________________________________ test_foo ___________________________________

    @mock_iam
    def test_foo():
        iam = boto3.client("iam")
        policy = """
        {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Sid": "economycloud",
                    "Action": [
                        "s3:List*",
                        "s3:Get*",
                    ],
                    "Effect": "Allow",
                    "Resource": [
                        "arn:aws:s3:::REPLC/*",
                        "arn:aws:s3:::REPLC"
                    ]
                },
                {
                    "Sid": "AllowListing",
                    "Action": [
                        "s3:ListBucket"
                    ],
                    "Effect": "Allow",
                    "Resource": [
                        "arn:aws:s3:::*"
                    ]
                },
            ]
        }
            """
        res0 = iam.create_policy(PolicyName="foo", PolicyDocument=policy)
        arn = res0['Policy']['Arn']
        version = res0['Policy']['DefaultVersionId']
>       res1 = iam.get_policy(PolicyArn=arn)

footest.py:48:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../envs/economy-cloud-MYowzdHw/lib/python3.6/site-packages/botocore/client.py:317: in _api_call
    return self._make_api_call(operation_name, kwargs)
../../envs/economy-cloud-MYowzdHw/lib/python3.6/site-packages/botocore/client.py:602: in _make_api_call
    operation_model, request_dict)
../../envs/economy-cloud-MYowzdHw/lib/python3.6/site-packages/botocore/endpoint.py:143: in make_request
    return self._send_request(request_dict, operation_model)
../../envs/economy-cloud-MYowzdHw/lib/python3.6/site-packages/botocore/endpoint.py:172: in _send_request
    success_response, exception):
../../envs/economy-cloud-MYowzdHw/lib/python3.6/site-packages/botocore/endpoint.py:265: in _needs_retry
    caught_exception=caught_exception, request_dict=request_dict)
../../envs/economy-cloud-MYowzdHw/lib/python3.6/site-packages/botocore/hooks.py:227: in emit
    return self._emit(event_name, kwargs)
../../envs/economy-cloud-MYowzdHw/lib/python3.6/site-packages/botocore/hooks.py:210: in _emit
    response = handler(**kwargs)
../../envs/economy-cloud-MYowzdHw/lib/python3.6/site-packages/botocore/retryhandler.py:183: in __call__
    if self._checker(attempts, response, caught_exception):
../../envs/economy-cloud-MYowzdHw/lib/python3.6/site-packages/botocore/retryhandler.py:251: in __call__
    caught_exception)
../../envs/economy-cloud-MYowzdHw/lib/python3.6/site-packages/botocore/retryhandler.py:269: in _should_retry
    return self._checker(attempt_number, response, caught_exception)
../../envs/economy-cloud-MYowzdHw/lib/python3.6/site-packages/botocore/retryhandler.py:317: in __call__
    caught_exception)
../../envs/economy-cloud-MYowzdHw/lib/python3.6/site-packages/botocore/retryhandler.py:223: in __call__
    attempt_number, caught_exception)
../../envs/economy-cloud-MYowzdHw/lib/python3.6/site-packages/botocore/retryhandler.py:359: in _check_caught_exception
    raise caught_exception
../../envs/economy-cloud-MYowzdHw/lib/python3.6/site-packages/botocore/endpoint.py:213: in _get_response
    proxies=self.proxies, timeout=self.timeout)
../../envs/economy-cloud-MYowzdHw/lib/python3.6/site-packages/botocore/vendored/requests/sessions.py:573: in send
    r = adapter.send(request, **kwargs)
../../envs/economy-cloud-MYowzdHw/lib/python3.6/site-packages/moto/packages/responses/responses.py:308: in unbound_on_send
    return self._on_request(adapter, request, *a, **kwargs)
../../envs/economy-cloud-MYowzdHw/lib/python3.6/site-packages/moto/packages/responses/responses.py:261: in _on_request
    status, r_headers, body = match['callback'](request)
../../envs/economy-cloud-MYowzdHw/lib/python3.6/site-packages/moto/core/utils.py:172: in __call__
    result = self.callback(request, request.url, request.headers)
../../envs/economy-cloud-MYowzdHw/lib/python3.6/site-packages/moto/core/responses.py:116: in dispatch
    return cls()._dispatch(*args, **kwargs)
../../envs/economy-cloud-MYowzdHw/lib/python3.6/site-packages/moto/core/responses.py:199: in _dispatch
    return self.call_action()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <moto.iam.responses.IamResponse object at 0x110ff8748>

    def call_action(self):
        headers = self.response_headers
        action = camelcase_to_underscores(self._get_action())
        method_names = method_names_from_class(self.__class__)
        if action in method_names:
            method = getattr(self, action)
            try:
                response = method()
            except HTTPException as http_error:
                response = http_error.description, dict(status=http_error.code)

            if isinstance(response, six.string_types):
                return 200, headers, response
            else:
                if len(response) == 2:
                    body, new_headers = response
                else:
                    status, new_headers, body = response
                status = new_headers.get('status', 200)
                headers.update(new_headers)
                # Cast status to string
                if "status" in headers:
                    headers['status'] = str(headers['status'])
                return status, headers, body

        if not action:
            return 404, headers, ''

        raise NotImplementedError(
>           "The {0} action has not been implemented".format(action))
E       NotImplementedError: The get_policy action has not been implemented

../../envs/economy-cloud-MYowzdHw/lib/python3.6/site-packages/moto/core/responses.py:295: NotImplementedError
=========================== 1 failed in 3.01 seconds ===========================
```

Is this related to #1238 ?



Discussion:

Resolving patch:
diff --git a/moto/iam/models.py b/moto/iam/models.py
--- a/moto/iam/models.py
+++ b/moto/iam/models.py
@@ -37,7 +37,6 @@ def __init__(self,
                  description=None,
                  document=None,
                  path=None):
-        self.document = document or {}
         self.name = name
 
         self.attachment_count = 0
@@ -45,7 +44,7 @@ def __init__(self,
         self.id = random_policy_id()
         self.path = path or '/'
         self.default_version_id = default_version_id or 'v1'
-        self.versions = []
+        self.versions = [PolicyVersion(self.arn, document, True)]
 
         self.create_datetime = datetime.now(pytz.utc)
         self.update_datetime = datetime.now(pytz.utc)
@@ -72,11 +71,11 @@ class ManagedPolicy(Policy):
 
     def attach_to(self, obj):
         self.attachment_count += 1
-        obj.managed_policies[self.name] = self
+        obj.managed_policies[self.arn] = self
 
     def detach_from(self, obj):
         self.attachment_count -= 1
-        del obj.managed_policies[self.name]
+        del obj.managed_policies[self.arn]
 
     @property
     def arn(self):
@@ -477,11 +476,13 @@ def create_policy(self, description, path, policy_document, policy_name):
             document=policy_document,
             path=path,
         )
-        self.managed_policies[policy.name] = policy
+        self.managed_policies[policy.arn] = policy
         return policy
 
-    def get_policy(self, policy_name):
-        return self.managed_policies.get(policy_name)
+    def get_policy(self, policy_arn):
+        if policy_arn not in self.managed_policies:
+            raise IAMNotFoundException("Policy {0} not found".format(policy_arn))
+        return self.managed_policies.get(policy_arn)
 
     def list_attached_role_policies(self, role_name, marker=None, max_items=100, path_prefix='/'):
         policies = self.get_role(role_name).managed_policies.values()
@@ -575,21 +576,18 @@ def list_role_policies(self, role_name):
         return role.policies.keys()
 
     def create_policy_version(self, policy_arn, policy_document, set_as_default):
-        policy_name = policy_arn.split(':')[-1]
-        policy_name = policy_name.split('/')[1]
-        policy = self.get_policy(policy_name)
+        policy = self.get_policy(policy_arn)
         if not policy:
             raise IAMNotFoundException("Policy not found")
         version = PolicyVersion(policy_arn, policy_document, set_as_default)
         policy.versions.append(version)
+        version.version_id = 'v{0}'.format(len(policy.versions))
         if set_as_default:
             policy.default_version_id = version.version_id
         return version
 
     def get_policy_version(self, policy_arn, version_id):
-        policy_name = policy_arn.split(':')[-1]
-        policy_name = policy_name.split('/')[1]
-        policy = self.get_policy(policy_name)
+        policy = self.get_policy(policy_arn)
         if not policy:
             raise IAMNotFoundException("Policy not found")
         for version in policy.versions:
@@ -598,19 +596,18 @@ def get_policy_version(self, policy_arn, version_id):
         raise IAMNotFoundException("Policy version not found")
 
     def list_policy_versions(self, policy_arn):
-        policy_name = policy_arn.split(':')[-1]
-        policy_name = policy_name.split('/')[1]
-        policy = self.get_policy(policy_name)
+        policy = self.get_policy(policy_arn)
         if not policy:
             raise IAMNotFoundException("Policy not found")
         return policy.versions
 
     def delete_policy_version(self, policy_arn, version_id):
-        policy_name = policy_arn.split(':')[-1]
-        policy_name = policy_name.split('/')[1]
-        policy = self.get_policy(policy_name)
+        policy = self.get_policy(policy_arn)
         if not policy:
             raise IAMNotFoundException("Policy not found")
+        if version_id == policy.default_version_id:
+            raise IAMConflictException(
+                "Cannot delete the default version of a policy")
         for i, v in enumerate(policy.versions):
             if v.version_id == version_id:
                 del policy.versions[i]
diff --git a/moto/iam/responses.py b/moto/iam/responses.py
--- a/moto/iam/responses.py
+++ b/moto/iam/responses.py
@@ -58,6 +58,12 @@ def create_policy(self):
         template = self.response_template(CREATE_POLICY_TEMPLATE)
         return template.render(policy=policy)
 
+    def get_policy(self):
+        policy_arn = self._get_param('PolicyArn')
+        policy = iam_backend.get_policy(policy_arn)
+        template = self.response_template(GET_POLICY_TEMPLATE)
+        return template.render(policy=policy)
+
     def list_attached_role_policies(self):
         marker = self._get_param('Marker')
         max_items = self._get_int_param('MaxItems', 100)
@@ -601,6 +607,25 @@ def get_account_authorization_details(self):
   </ResponseMetadata>
 </CreatePolicyResponse>"""
 
+GET_POLICY_TEMPLATE = """<GetPolicyResponse>
+  <GetPolicyResult>
+    <Policy>
+      <PolicyName>{{ policy.name }}</PolicyName>
+      <Description>{{ policy.description }}</Description>
+      <DefaultVersionId>{{ policy.default_version_id }}</DefaultVersionId>
+      <PolicyId>{{ policy.id }}</PolicyId>
+      <Path>{{ policy.path }}</Path>
+      <Arn>{{ policy.arn }}</Arn>
+      <AttachmentCount>{{ policy.attachment_count }}</AttachmentCount>
+      <CreateDate>{{ policy.create_datetime.isoformat() }}</CreateDate>
+      <UpdateDate>{{ policy.update_datetime.isoformat() }}</UpdateDate>
+    </Policy>
+  </GetPolicyResult>
+  <ResponseMetadata>
+    <RequestId>684f0917-3d22-11e4-a4a0-cffb9EXAMPLE</RequestId>
+  </ResponseMetadata>
+</GetPolicyResponse>"""
+
 LIST_ATTACHED_ROLE_POLICIES_TEMPLATE = """<ListAttachedRolePoliciesResponse>
   <ListAttachedRolePoliciesResult>
     {% if marker is none %}


Test patch:
diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py
--- a/tests/test_iam/test_iam.py
+++ b/tests/test_iam/test_iam.py
@@ -286,6 +286,16 @@ def test_create_policy_versions():
         PolicyDocument='{"some":"policy"}')
     version.get('PolicyVersion').get('Document').should.equal({'some': 'policy'})
 
+@mock_iam
+def test_get_policy():
+    conn = boto3.client('iam', region_name='us-east-1')
+    response = conn.create_policy(
+        PolicyName="TestGetPolicy",
+        PolicyDocument='{"some":"policy"}')
+    policy = conn.get_policy(
+        PolicyArn="arn:aws:iam::123456789012:policy/TestGetPolicy")
+    response['Policy']['Arn'].should.equal("arn:aws:iam::123456789012:policy/TestGetPolicy")
+
 
 @mock_iam
 def test_get_policy_version():
@@ -314,17 +324,22 @@ def test_list_policy_versions():
             PolicyArn="arn:aws:iam::123456789012:policy/TestListPolicyVersions")
     conn.create_policy(
         PolicyName="TestListPolicyVersions",
-        PolicyDocument='{"some":"policy"}')
-    conn.create_policy_version(
-        PolicyArn="arn:aws:iam::123456789012:policy/TestListPolicyVersions",
         PolicyDocument='{"first":"policy"}')
+    versions = conn.list_policy_versions(
+        PolicyArn="arn:aws:iam::123456789012:policy/TestListPolicyVersions")
+    versions.get('Versions')[0].get('VersionId').should.equal('v1')
+    
     conn.create_policy_version(
         PolicyArn="arn:aws:iam::123456789012:policy/TestListPolicyVersions",
         PolicyDocument='{"second":"policy"}')
+    conn.create_policy_version(
+        PolicyArn="arn:aws:iam::123456789012:policy/TestListPolicyVersions",
+        PolicyDocument='{"third":"policy"}')
     versions = conn.list_policy_versions(
         PolicyArn="arn:aws:iam::123456789012:policy/TestListPolicyVersions")
-    versions.get('Versions')[0].get('Document').should.equal({'first': 'policy'})
+    print(versions.get('Versions'))
     versions.get('Versions')[1].get('Document').should.equal({'second': 'policy'})
+    versions.get('Versions')[2].get('Document').should.equal({'third': 'policy'})
 
 
 @mock_iam
@@ -332,20 +347,20 @@ def test_delete_policy_version():
     conn = boto3.client('iam', region_name='us-east-1')
     conn.create_policy(
         PolicyName="TestDeletePolicyVersion",
-        PolicyDocument='{"some":"policy"}')
+        PolicyDocument='{"first":"policy"}')
     conn.create_policy_version(
         PolicyArn="arn:aws:iam::123456789012:policy/TestDeletePolicyVersion",
-        PolicyDocument='{"first":"policy"}')
+        PolicyDocument='{"second":"policy"}')
     with assert_raises(ClientError):
         conn.delete_policy_version(
             PolicyArn="arn:aws:iam::123456789012:policy/TestDeletePolicyVersion",
             VersionId='v2-nope-this-does-not-exist')
     conn.delete_policy_version(
         PolicyArn="arn:aws:iam::123456789012:policy/TestDeletePolicyVersion",
-        VersionId='v1')
+        VersionId='v2')
     versions = conn.list_policy_versions(
         PolicyArn="arn:aws:iam::123456789012:policy/TestDeletePolicyVersion")
-    len(versions.get('Versions')).should.equal(0)
+    len(versions.get('Versions')).should.equal(1)
 
 
 @mock_iam_deprecated()