We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
AWS::ECR::Repository RepositoryPolicyText doesn't need 'Resource'
*cfn-lint version: (`cfn-lint --version`)* 0.7.1

*Description of issue.*

When upgrading to 0.7.1 my template with a valid policy adapted from [step 3k of this tutorial](https://docs.aws.amazon.com/codebuild/latest/userguide/sample-ecr.html) triggers "E2507 IAM Policy statement missing Resource or NotResource". This kind of policy apparently doesn't need a `Resource` key, so this shouldn't happen.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/cfnlint/rules/resources/iam/Policy.py`
Content:
```
1 """
2   Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 
4   Permission is hereby granted, free of charge, to any person obtaining a copy of this
5   software and associated documentation files (the "Software"), to deal in the Software
6   without restriction, including without limitation the rights to use, copy, modify,
7   merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
8   permit persons to whom the Software is furnished to do so.
9 
10   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
11   INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
12   PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
13   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
14   OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
15   SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16 """
17 from datetime import date
18 from cfnlint import CloudFormationLintRule
19 from cfnlint import RuleMatch
20 
21 
22 class Policy(CloudFormationLintRule):
23     """Check if IAM Policy JSON is correct"""
24     id = 'E2507'
25     shortdesc = 'Check if IAM Policies are properly configured'
26     description = 'See if there elements inside an IAM policy ' + \
27                   'are correct'
28     source_url = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-policy.html'
29     tags = ['properties', 'iam']
30 
31     def __init__(self):
32         """Init"""
33         self.resources_and_keys = {
34             'AWS::SNS::TopicPolicy': 'PolicyDocument',
35             'AWS::S3::BucketPolicy': 'PolicyDocument',
36             'AWS::KMS::Key': 'KeyPolicy',
37             'AWS::SQS::QueuePolicy': 'PolicyDocument',
38             'AWS::ECR::Repository': 'RepositoryPolicyText',
39             'AWS::Elasticsearch::Domain': 'AccessPolicies',
40         }
41         self.idp_and_keys = {
42             'AWS::IAM::Group': 'Policies',
43             'AWS::IAM::ManagedPolicy': 'PolicyDocument',
44             'AWS::IAM::Policy': 'PolicyDocument',
45             'AWS::IAM::Role': 'Policies',
46             'AWS::IAM::User': 'Policies',
47         }
48         for resource_type in self.resources_and_keys:
49             self.resource_property_types.append(resource_type)
50         for resource_type in self.idp_and_keys:
51             self.resource_property_types.append(resource_type)
52 
53     def check_policy_document(self, value, path, cfn, is_identity_policy):
54         """Check policy document"""
55         matches = []
56 
57         valid_keys = [
58             'Version',
59             'Id',
60             'Statement',
61         ]
62         valid_versions = ['2012-10-17', '2008-10-17', date(2012, 10, 17), date(2008, 10, 17)]
63 
64         if not isinstance(value, dict):
65             message = 'IAM Policy Documents needs to be JSON'
66             matches.append(
67                 RuleMatch(path[:], message))
68             return matches
69 
70         for parent_key, parent_value in value.items():
71             if parent_key not in valid_keys:
72                 message = 'IAM Policy key %s doesn\'t exist.' % (parent_key)
73                 matches.append(
74                     RuleMatch(path[:] + [parent_key], message))
75             if parent_key == 'Version':
76                 if parent_value not in valid_versions:
77                     message = 'IAM Policy Version needs to be one of (%s).' % (
78                         ', '.join(map(str, ['2012-10-17', '2008-10-17'])))
79                     matches.append(
80                         RuleMatch(path[:] + [parent_key], message))
81             if parent_key == 'Statement':
82                 if isinstance(parent_value, (list)):
83                     statements = cfn.get_values(value, 'Statement', path[:])
84                     for statement in statements:
85                         matches.extend(
86                             self._check_policy_statement(
87                                 statement['Path'], statement['Value'], is_identity_policy
88                             )
89                         )
90                 else:
91                     message = 'IAM Policy statement should be of list.'
92                     matches.append(
93                         RuleMatch(path[:] + [parent_key], message))
94         return matches
95 
96     def _check_policy_statement(self, branch, statement, is_identity_policy):
97         """Check statements"""
98         matches = []
99         statement_valid_keys = [
100             'Effect',
101             'Principal',
102             'NotPrincipal',
103             'Action',
104             'NotAction',
105             'Resource',
106             'NotResource',
107             'Condition',
108             'Sid',
109         ]
110 
111         for key, _ in statement.items():
112             if key not in statement_valid_keys:
113                 message = 'IAM Policy statement key %s isn\'t valid' % (key)
114                 matches.append(
115                     RuleMatch(branch[:] + [key], message))
116         if 'Effect' not in statement:
117             message = 'IAM Policy statement missing Effect'
118             matches.append(
119                 RuleMatch(branch[:], message))
120         else:
121             effect = statement.get('Effect')
122             if effect not in ['Allow', 'Deny']:
123                 message = 'IAM Policy Effect should be Allow or Deny'
124                 matches.append(
125                     RuleMatch(branch[:] + ['Effect'], message))
126         if 'Action' not in statement and 'NotAction' not in statement:
127             message = 'IAM Policy statement missing Action or NotAction'
128             matches.append(
129                 RuleMatch(branch[:], message))
130         if is_identity_policy:
131             if 'Principal' in statement or 'NotPrincipal' in statement:
132                 message = 'IAM Resource Policy statement shouldn\'t have Principal or NotPrincipal'
133                 matches.append(
134                     RuleMatch(branch[:], message))
135         else:
136             if 'Principal' not in statement and 'NotPrincipal' not in statement:
137                 message = 'IAM Resource Policy statement should have Principal or NotPrincipal'
138                 matches.append(
139                     RuleMatch(branch[:] + ['Principal'], message))
140         if 'Resource' not in statement and 'NotResource' not in statement:
141             message = 'IAM Policy statement missing Resource or NotResource'
142             matches.append(
143                 RuleMatch(branch[:], message))
144 
145         return(matches)
146 
147     def match_resource_properties(self, properties, resourcetype, path, cfn):
148         """Check CloudFormation Properties"""
149         matches = []
150 
151         is_identity_policy = True
152         if resourcetype in self.resources_and_keys:
153             is_identity_policy = False
154 
155         key = None
156         if resourcetype in self.resources_and_keys:
157             key = self.resources_and_keys.get(resourcetype)
158         else:
159             key = self.idp_and_keys.get(resourcetype)
160 
161         if not key:
162             # Key isn't defined return nothing
163             return matches
164 
165         if key == 'Policies':
166             for index, policy in enumerate(properties.get(key, [])):
167                 matches.extend(
168                     cfn.check_value(
169                         obj=policy, key='PolicyDocument',
170                         path=path[:] + ['Policies', index],
171                         check_value=self.check_policy_document,
172                         cfn=cfn,
173                         is_identity_policy=is_identity_policy
174                     ))
175         else:
176             matches.extend(
177                 cfn.check_value(
178                     obj=properties, key=key,
179                     path=path[:],
180                     check_value=self.check_policy_document,
181                     cfn=cfn,
182                     is_identity_policy=is_identity_policy
183                 ))
184 
185         return matches
186
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/cfnlint/rules/resources/iam/Policy.py b/src/cfnlint/rules/resources/iam/Policy.py
--- a/src/cfnlint/rules/resources/iam/Policy.py
+++ b/src/cfnlint/rules/resources/iam/Policy.py
@@ -30,6 +30,9 @@
 
     def __init__(self):
         """Init"""
+        self.resource_exceptions = {
+            'AWS::ECR::Repository': 'RepositoryPolicyText',
+        }
         self.resources_and_keys = {
             'AWS::SNS::TopicPolicy': 'PolicyDocument',
             'AWS::S3::BucketPolicy': 'PolicyDocument',
@@ -50,7 +53,7 @@
         for resource_type in self.idp_and_keys:
             self.resource_property_types.append(resource_type)
 
-    def check_policy_document(self, value, path, cfn, is_identity_policy):
+    def check_policy_document(self, value, path, cfn, is_identity_policy, resource_exceptions):
         """Check policy document"""
         matches = []
 
@@ -84,7 +87,7 @@
                     for statement in statements:
                         matches.extend(
                             self._check_policy_statement(
-                                statement['Path'], statement['Value'], is_identity_policy
+                                statement['Path'], statement['Value'], is_identity_policy, resource_exceptions
                             )
                         )
                 else:
@@ -93,7 +96,7 @@
                         RuleMatch(path[:] + [parent_key], message))
         return matches
 
-    def _check_policy_statement(self, branch, statement, is_identity_policy):
+    def _check_policy_statement(self, branch, statement, is_identity_policy, resource_exceptions):
         """Check statements"""
         matches = []
         statement_valid_keys = [
@@ -137,10 +140,11 @@
                 message = 'IAM Resource Policy statement should have Principal or NotPrincipal'
                 matches.append(
                     RuleMatch(branch[:] + ['Principal'], message))
-        if 'Resource' not in statement and 'NotResource' not in statement:
-            message = 'IAM Policy statement missing Resource or NotResource'
-            matches.append(
-                RuleMatch(branch[:], message))
+        if not resource_exceptions:
+            if 'Resource' not in statement and 'NotResource' not in statement:
+                message = 'IAM Policy statement missing Resource or NotResource'
+                matches.append(
+                    RuleMatch(branch[:], message))
 
         return(matches)
 
@@ -162,6 +166,10 @@
             # Key isn't defined return nothing
             return matches
 
+        resource_exceptions = False
+        if key == self.resource_exceptions.get(resourcetype):
+            resource_exceptions = True
+
         if key == 'Policies':
             for index, policy in enumerate(properties.get(key, [])):
                 matches.extend(
@@ -170,7 +178,8 @@
                         path=path[:] + ['Policies', index],
                         check_value=self.check_policy_document,
                         cfn=cfn,
-                        is_identity_policy=is_identity_policy
+                        is_identity_policy=is_identity_policy,
+                        resource_exceptions=resource_exceptions,
                     ))
         else:
             matches.extend(
@@ -179,7 +188,8 @@
                     path=path[:],
                     check_value=self.check_policy_document,
                     cfn=cfn,
-                    is_identity_policy=is_identity_policy
+                    is_identity_policy=is_identity_policy,
+                    resource_exceptions=resource_exceptions,
                 ))
 
         return matches

{"golden_diff": "diff --git a/src/cfnlint/rules/resources/iam/Policy.py b/src/cfnlint/rules/resources/iam/Policy.py\n--- a/src/cfnlint/rules/resources/iam/Policy.py\n+++ b/src/cfnlint/rules/resources/iam/Policy.py\n@@ -30,6 +30,9 @@\n \n     def __init__(self):\n         \"\"\"Init\"\"\"\n+        self.resource_exceptions = {\n+            'AWS::ECR::Repository': 'RepositoryPolicyText',\n+        }\n         self.resources_and_keys = {\n             'AWS::SNS::TopicPolicy': 'PolicyDocument',\n             'AWS::S3::BucketPolicy': 'PolicyDocument',\n@@ -50,7 +53,7 @@\n         for resource_type in self.idp_and_keys:\n             self.resource_property_types.append(resource_type)\n \n-    def check_policy_document(self, value, path, cfn, is_identity_policy):\n+    def check_policy_document(self, value, path, cfn, is_identity_policy, resource_exceptions):\n         \"\"\"Check policy document\"\"\"\n         matches = []\n \n@@ -84,7 +87,7 @@\n                     for statement in statements:\n                         matches.extend(\n                             self._check_policy_statement(\n-                                statement['Path'], statement['Value'], is_identity_policy\n+                                statement['Path'], statement['Value'], is_identity_policy, resource_exceptions\n                             )\n                         )\n                 else:\n@@ -93,7 +96,7 @@\n                         RuleMatch(path[:] + [parent_key], message))\n         return matches\n \n-    def _check_policy_statement(self, branch, statement, is_identity_policy):\n+    def _check_policy_statement(self, branch, statement, is_identity_policy, resource_exceptions):\n         \"\"\"Check statements\"\"\"\n         matches = []\n         statement_valid_keys = [\n@@ -137,10 +140,11 @@\n                 message = 'IAM Resource Policy statement should have Principal or NotPrincipal'\n                 matches.append(\n                     RuleMatch(branch[:] + ['Principal'], message))\n-        if 'Resource' not in statement and 'NotResource' not in statement:\n-            message = 'IAM Policy statement missing Resource or NotResource'\n-            matches.append(\n-                RuleMatch(branch[:], message))\n+        if not resource_exceptions:\n+            if 'Resource' not in statement and 'NotResource' not in statement:\n+                message = 'IAM Policy statement missing Resource or NotResource'\n+                matches.append(\n+                    RuleMatch(branch[:], message))\n \n         return(matches)\n \n@@ -162,6 +166,10 @@\n             # Key isn't defined return nothing\n             return matches\n \n+        resource_exceptions = False\n+        if key == self.resource_exceptions.get(resourcetype):\n+            resource_exceptions = True\n+\n         if key == 'Policies':\n             for index, policy in enumerate(properties.get(key, [])):\n                 matches.extend(\n@@ -170,7 +178,8 @@\n                         path=path[:] + ['Policies', index],\n                         check_value=self.check_policy_document,\n                         cfn=cfn,\n-                        is_identity_policy=is_identity_policy\n+                        is_identity_policy=is_identity_policy,\n+                        resource_exceptions=resource_exceptions,\n                     ))\n         else:\n             matches.extend(\n@@ -179,7 +188,8 @@\n                     path=path[:],\n                     check_value=self.check_policy_document,\n                     cfn=cfn,\n-                    is_identity_policy=is_identity_policy\n+                    is_identity_policy=is_identity_policy,\n+                    resource_exceptions=resource_exceptions,\n                 ))\n \n         return matches\n", "issue": "AWS::ECR::Repository RepositoryPolicyText doesn't need 'Resource'\n*cfn-lint version: (`cfn-lint --version`)* 0.7.1\r\n\r\n*Description of issue.*\r\n\r\nWhen upgrading to 0.7.1 my template with a valid policy adapted from [step 3k of this tutorial](https://docs.aws.amazon.com/codebuild/latest/userguide/sample-ecr.html) triggers \"E2507 IAM Policy statement missing Resource or NotResource\". This kind of policy apparently doesn't need a `Resource` key, so this shouldn't happen.\n", "before_files": [{"content": "\"\"\"\n  Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n\n  Permission is hereby granted, free of charge, to any person obtaining a copy of this\n  software and associated documentation files (the \"Software\"), to deal in the Software\n  without restriction, including without limitation the rights to use, copy, modify,\n  merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n  permit persons to whom the Software is furnished to do so.\n\n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n  PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\"\"\"\nfrom datetime import date\nfrom cfnlint import CloudFormationLintRule\nfrom cfnlint import RuleMatch\n\n\nclass Policy(CloudFormationLintRule):\n    \"\"\"Check if IAM Policy JSON is correct\"\"\"\n    id = 'E2507'\n    shortdesc = 'Check if IAM Policies are properly configured'\n    description = 'See if there elements inside an IAM policy ' + \\\n                  'are correct'\n    source_url = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-policy.html'\n    tags = ['properties', 'iam']\n\n    def __init__(self):\n        \"\"\"Init\"\"\"\n        self.resources_and_keys = {\n            'AWS::SNS::TopicPolicy': 'PolicyDocument',\n            'AWS::S3::BucketPolicy': 'PolicyDocument',\n            'AWS::KMS::Key': 'KeyPolicy',\n            'AWS::SQS::QueuePolicy': 'PolicyDocument',\n            'AWS::ECR::Repository': 'RepositoryPolicyText',\n            'AWS::Elasticsearch::Domain': 'AccessPolicies',\n        }\n        self.idp_and_keys = {\n            'AWS::IAM::Group': 'Policies',\n            'AWS::IAM::ManagedPolicy': 'PolicyDocument',\n            'AWS::IAM::Policy': 'PolicyDocument',\n            'AWS::IAM::Role': 'Policies',\n            'AWS::IAM::User': 'Policies',\n        }\n        for resource_type in self.resources_and_keys:\n            self.resource_property_types.append(resource_type)\n        for resource_type in self.idp_and_keys:\n            self.resource_property_types.append(resource_type)\n\n    def check_policy_document(self, value, path, cfn, is_identity_policy):\n        \"\"\"Check policy document\"\"\"\n        matches = []\n\n        valid_keys = [\n            'Version',\n            'Id',\n            'Statement',\n        ]\n        valid_versions = ['2012-10-17', '2008-10-17', date(2012, 10, 17), date(2008, 10, 17)]\n\n        if not isinstance(value, dict):\n            message = 'IAM Policy Documents needs to be JSON'\n            matches.append(\n                RuleMatch(path[:], message))\n            return matches\n\n        for parent_key, parent_value in value.items():\n            if parent_key not in valid_keys:\n                message = 'IAM Policy key %s doesn\\'t exist.' % (parent_key)\n                matches.append(\n                    RuleMatch(path[:] + [parent_key], message))\n            if parent_key == 'Version':\n                if parent_value not in valid_versions:\n                    message = 'IAM Policy Version needs to be one of (%s).' % (\n                        ', '.join(map(str, ['2012-10-17', '2008-10-17'])))\n                    matches.append(\n                        RuleMatch(path[:] + [parent_key], message))\n            if parent_key == 'Statement':\n                if isinstance(parent_value, (list)):\n                    statements = cfn.get_values(value, 'Statement', path[:])\n                    for statement in statements:\n                        matches.extend(\n                            self._check_policy_statement(\n                                statement['Path'], statement['Value'], is_identity_policy\n                            )\n                        )\n                else:\n                    message = 'IAM Policy statement should be of list.'\n                    matches.append(\n                        RuleMatch(path[:] + [parent_key], message))\n        return matches\n\n    def _check_policy_statement(self, branch, statement, is_identity_policy):\n        \"\"\"Check statements\"\"\"\n        matches = []\n        statement_valid_keys = [\n            'Effect',\n            'Principal',\n            'NotPrincipal',\n            'Action',\n            'NotAction',\n            'Resource',\n            'NotResource',\n            'Condition',\n            'Sid',\n        ]\n\n        for key, _ in statement.items():\n            if key not in statement_valid_keys:\n                message = 'IAM Policy statement key %s isn\\'t valid' % (key)\n                matches.append(\n                    RuleMatch(branch[:] + [key], message))\n        if 'Effect' not in statement:\n            message = 'IAM Policy statement missing Effect'\n            matches.append(\n                RuleMatch(branch[:], message))\n        else:\n            effect = statement.get('Effect')\n            if effect not in ['Allow', 'Deny']:\n                message = 'IAM Policy Effect should be Allow or Deny'\n                matches.append(\n                    RuleMatch(branch[:] + ['Effect'], message))\n        if 'Action' not in statement and 'NotAction' not in statement:\n            message = 'IAM Policy statement missing Action or NotAction'\n            matches.append(\n                RuleMatch(branch[:], message))\n        if is_identity_policy:\n            if 'Principal' in statement or 'NotPrincipal' in statement:\n                message = 'IAM Resource Policy statement shouldn\\'t have Principal or NotPrincipal'\n                matches.append(\n                    RuleMatch(branch[:], message))\n        else:\n            if 'Principal' not in statement and 'NotPrincipal' not in statement:\n                message = 'IAM Resource Policy statement should have Principal or NotPrincipal'\n                matches.append(\n                    RuleMatch(branch[:] + ['Principal'], message))\n        if 'Resource' not in statement and 'NotResource' not in statement:\n            message = 'IAM Policy statement missing Resource or NotResource'\n            matches.append(\n                RuleMatch(branch[:], message))\n\n        return(matches)\n\n    def match_resource_properties(self, properties, resourcetype, path, cfn):\n        \"\"\"Check CloudFormation Properties\"\"\"\n        matches = []\n\n        is_identity_policy = True\n        if resourcetype in self.resources_and_keys:\n            is_identity_policy = False\n\n        key = None\n        if resourcetype in self.resources_and_keys:\n            key = self.resources_and_keys.get(resourcetype)\n        else:\n            key = self.idp_and_keys.get(resourcetype)\n\n        if not key:\n            # Key isn't defined return nothing\n            return matches\n\n        if key == 'Policies':\n            for index, policy in enumerate(properties.get(key, [])):\n                matches.extend(\n                    cfn.check_value(\n                        obj=policy, key='PolicyDocument',\n                        path=path[:] + ['Policies', index],\n                        check_value=self.check_policy_document,\n                        cfn=cfn,\n                        is_identity_policy=is_identity_policy\n                    ))\n        else:\n            matches.extend(\n                cfn.check_value(\n                    obj=properties, key=key,\n                    path=path[:],\n                    check_value=self.check_policy_document,\n                    cfn=cfn,\n                    is_identity_policy=is_identity_policy\n                ))\n\n        return matches\n", "path": "src/cfnlint/rules/resources/iam/Policy.py"}], "after_files": [{"content": "\"\"\"\n  Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n\n  Permission is hereby granted, free of charge, to any person obtaining a copy of this\n  software and associated documentation files (the \"Software\"), to deal in the Software\n  without restriction, including without limitation the rights to use, copy, modify,\n  merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n  permit persons to whom the Software is furnished to do so.\n\n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n  PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\"\"\"\nfrom datetime import date\nfrom cfnlint import CloudFormationLintRule\nfrom cfnlint import RuleMatch\n\n\nclass Policy(CloudFormationLintRule):\n    \"\"\"Check if IAM Policy JSON is correct\"\"\"\n    id = 'E2507'\n    shortdesc = 'Check if IAM Policies are properly configured'\n    description = 'See if there elements inside an IAM policy ' + \\\n                  'are correct'\n    source_url = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-policy.html'\n    tags = ['properties', 'iam']\n\n    def __init__(self):\n        \"\"\"Init\"\"\"\n        self.resource_exceptions = {\n            'AWS::ECR::Repository': 'RepositoryPolicyText',\n        }\n        self.resources_and_keys = {\n            'AWS::SNS::TopicPolicy': 'PolicyDocument',\n            'AWS::S3::BucketPolicy': 'PolicyDocument',\n            'AWS::KMS::Key': 'KeyPolicy',\n            'AWS::SQS::QueuePolicy': 'PolicyDocument',\n            'AWS::ECR::Repository': 'RepositoryPolicyText',\n            'AWS::Elasticsearch::Domain': 'AccessPolicies',\n        }\n        self.idp_and_keys = {\n            'AWS::IAM::Group': 'Policies',\n            'AWS::IAM::ManagedPolicy': 'PolicyDocument',\n            'AWS::IAM::Policy': 'PolicyDocument',\n            'AWS::IAM::Role': 'Policies',\n            'AWS::IAM::User': 'Policies',\n        }\n        for resource_type in self.resources_and_keys:\n            self.resource_property_types.append(resource_type)\n        for resource_type in self.idp_and_keys:\n            self.resource_property_types.append(resource_type)\n\n    def check_policy_document(self, value, path, cfn, is_identity_policy, resource_exceptions):\n        \"\"\"Check policy document\"\"\"\n        matches = []\n\n        valid_keys = [\n            'Version',\n            'Id',\n            'Statement',\n        ]\n        valid_versions = ['2012-10-17', '2008-10-17', date(2012, 10, 17), date(2008, 10, 17)]\n\n        if not isinstance(value, dict):\n            message = 'IAM Policy Documents needs to be JSON'\n            matches.append(\n                RuleMatch(path[:], message))\n            return matches\n\n        for parent_key, parent_value in value.items():\n            if parent_key not in valid_keys:\n                message = 'IAM Policy key %s doesn\\'t exist.' % (parent_key)\n                matches.append(\n                    RuleMatch(path[:] + [parent_key], message))\n            if parent_key == 'Version':\n                if parent_value not in valid_versions:\n                    message = 'IAM Policy Version needs to be one of (%s).' % (\n                        ', '.join(map(str, ['2012-10-17', '2008-10-17'])))\n                    matches.append(\n                        RuleMatch(path[:] + [parent_key], message))\n            if parent_key == 'Statement':\n                if isinstance(parent_value, (list)):\n                    statements = cfn.get_values(value, 'Statement', path[:])\n                    for statement in statements:\n                        matches.extend(\n                            self._check_policy_statement(\n                                statement['Path'], statement['Value'], is_identity_policy, resource_exceptions\n                            )\n                        )\n                else:\n                    message = 'IAM Policy statement should be of list.'\n                    matches.append(\n                        RuleMatch(path[:] + [parent_key], message))\n        return matches\n\n    def _check_policy_statement(self, branch, statement, is_identity_policy, resource_exceptions):\n        \"\"\"Check statements\"\"\"\n        matches = []\n        statement_valid_keys = [\n            'Effect',\n            'Principal',\n            'NotPrincipal',\n            'Action',\n            'NotAction',\n            'Resource',\n            'NotResource',\n            'Condition',\n            'Sid',\n        ]\n\n        for key, _ in statement.items():\n            if key not in statement_valid_keys:\n                message = 'IAM Policy statement key %s isn\\'t valid' % (key)\n                matches.append(\n                    RuleMatch(branch[:] + [key], message))\n        if 'Effect' not in statement:\n            message = 'IAM Policy statement missing Effect'\n            matches.append(\n                RuleMatch(branch[:], message))\n        else:\n            effect = statement.get('Effect')\n            if effect not in ['Allow', 'Deny']:\n                message = 'IAM Policy Effect should be Allow or Deny'\n                matches.append(\n                    RuleMatch(branch[:] + ['Effect'], message))\n        if 'Action' not in statement and 'NotAction' not in statement:\n            message = 'IAM Policy statement missing Action or NotAction'\n            matches.append(\n                RuleMatch(branch[:], message))\n        if is_identity_policy:\n            if 'Principal' in statement or 'NotPrincipal' in statement:\n                message = 'IAM Resource Policy statement shouldn\\'t have Principal or NotPrincipal'\n                matches.append(\n                    RuleMatch(branch[:], message))\n        else:\n            if 'Principal' not in statement and 'NotPrincipal' not in statement:\n                message = 'IAM Resource Policy statement should have Principal or NotPrincipal'\n                matches.append(\n                    RuleMatch(branch[:] + ['Principal'], message))\n        if not resource_exceptions:\n            if 'Resource' not in statement and 'NotResource' not in statement:\n                message = 'IAM Policy statement missing Resource or NotResource'\n                matches.append(\n                    RuleMatch(branch[:], message))\n\n        return(matches)\n\n    def match_resource_properties(self, properties, resourcetype, path, cfn):\n        \"\"\"Check CloudFormation Properties\"\"\"\n        matches = []\n\n        is_identity_policy = True\n        if resourcetype in self.resources_and_keys:\n            is_identity_policy = False\n\n        key = None\n        if resourcetype in self.resources_and_keys:\n            key = self.resources_and_keys.get(resourcetype)\n        else:\n            key = self.idp_and_keys.get(resourcetype)\n\n        if not key:\n            # Key isn't defined return nothing\n            return matches\n\n        resource_exceptions = False\n        if key == self.resource_exceptions.get(resourcetype):\n            resource_exceptions = True\n\n        if key == 'Policies':\n            for index, policy in enumerate(properties.get(key, [])):\n                matches.extend(\n                    cfn.check_value(\n                        obj=policy, key='PolicyDocument',\n                        path=path[:] + ['Policies', index],\n                        check_value=self.check_policy_document,\n                        cfn=cfn,\n                        is_identity_policy=is_identity_policy,\n                        resource_exceptions=resource_exceptions,\n                    ))\n        else:\n            matches.extend(\n                cfn.check_value(\n                    obj=properties, key=key,\n                    path=path[:],\n                    check_value=self.check_policy_document,\n                    cfn=cfn,\n                    is_identity_policy=is_identity_policy,\n                    resource_exceptions=resource_exceptions,\n                ))\n\n        return matches\n", "path": "src/cfnlint/rules/resources/iam/Policy.py"}]}