We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Linter does not catch MalformedPolicyDocument syntax error
*cfn-lint version: 0.25.0*

*Description of issue.*
`cfn-lint` fails to catch the following syntax error pre-deployment, which is caught by CloudFormation during deployment:
```
Syntax errors in policy. (Service: AmazonIdentityManagement; Status Code: 400; Error Code: MalformedPolicyDocument; Request ID: ccd05e19-aa60-4b26-bfcc-8fbafca40c61; Proxy: null)
```

The policy in question has the following template:
```yaml
APILambdaExecutionPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      Description: Allows API Gateway triggered Lambdas access to service resources
      Path: /
      ManagedPolicyName:
        Fn::Sub: ${Project}-APILambdaExecutionPolicy-${AWS::Region}
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Action:
          - dynamodb:*Get*
          - dynamodb:*Item*
          - dynamodb:Describe*
          - dynamodb:Query
          Resource:
          - Fn::GetAtt:
            - DynamoDB
            - Outputs.TaskDBTableARN
          - Fn::Sub: ${DynamoDB.Outputs.TaskDBTableARN}/*
          - Effect: Allow
            Action:
            - cloudwatch:PutMetricAlarm
            - cloudwatch:PutMetricData
            Resource:
            - '*'
          - Effect: Allow
            Action:
            - xray:PutTraceSegments
            - xray:PutTelemetryRecords
            Resource:
            - '*'
          - Effect: Allow
            Action:
            - sqs:GetQueueUrl
            - sqs:SendMessage
            Resource:
            - Fn::GetAtt:
              - SQS
              - Outputs.QueueARN
          - Effect: Allow
            Action:
            - sns:Publish
            Resource:
            - arn:aws:sns:*:*:*  
```




--- 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 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 SPDX-License-Identifier: MIT-0
4 """
5 import json
6 from datetime import date
7 import six
8 from cfnlint.helpers import convert_dict
9 from cfnlint.rules import CloudFormationLintRule
10 from cfnlint.rules import RuleMatch
11 
12 
13 class Policy(CloudFormationLintRule):
14     """Check if IAM Policy JSON is correct"""
15     id = 'E2507'
16     shortdesc = 'Check if IAM Policies are properly configured'
17     description = 'See if there elements inside an IAM policy are correct'
18     source_url = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-policy.html'
19     tags = ['properties', 'iam']
20 
21     def __init__(self):
22         """Init"""
23         super(Policy, self).__init__()
24         self.resource_exceptions = {
25             'AWS::ECR::Repository': 'RepositoryPolicyText',
26         }
27         self.resources_and_keys = {
28             'AWS::ECR::Repository': 'RepositoryPolicyText',
29             'AWS::Elasticsearch::Domain': 'AccessPolicies',
30             'AWS::KMS::Key': 'KeyPolicy',
31             'AWS::S3::BucketPolicy': 'PolicyDocument',
32             'AWS::SNS::TopicPolicy': 'PolicyDocument',
33             'AWS::SQS::QueuePolicy': 'PolicyDocument',
34         }
35         self.idp_and_keys = {
36             'AWS::IAM::Group': 'Policies',
37             'AWS::IAM::ManagedPolicy': 'PolicyDocument',
38             'AWS::IAM::Policy': 'PolicyDocument',
39             'AWS::IAM::Role': 'Policies',
40             'AWS::IAM::User': 'Policies',
41             'AWS::SSO::PermissionSet': 'InlinePolicy',
42         }
43         for resource_type in self.resources_and_keys:
44             self.resource_property_types.append(resource_type)
45         for resource_type in self.idp_and_keys:
46             self.resource_property_types.append(resource_type)
47 
48     def check_policy_document(self, value, path, is_identity_policy, resource_exceptions, start_mark, end_mark):
49         """Check policy document"""
50         matches = []
51 
52         valid_keys = [
53             'Version',
54             'Id',
55             'Statement',
56         ]
57         valid_versions = ['2012-10-17', '2008-10-17', date(2012, 10, 17), date(2008, 10, 17)]
58 
59         if isinstance(value, six.string_types):
60             try:
61                 value = convert_dict(json.loads(value), start_mark, end_mark)
62             except Exception as ex:  # pylint: disable=W0703,W0612
63                 message = 'IAM Policy Documents need to be JSON'
64                 matches.append(RuleMatch(path[:], message))
65                 return matches
66 
67         if not isinstance(value, dict):
68             message = 'IAM Policy Documents needs to be JSON'
69             matches.append(
70                 RuleMatch(path[:], message))
71             return matches
72 
73         for p_vs, p_p in value.items_safe(path[:], (dict)):
74             for parent_key, parent_value in p_vs.items():
75                 if parent_key not in valid_keys:
76                     message = 'IAM Policy key %s doesn\'t exist.' % (parent_key)
77                     matches.append(
78                         RuleMatch(path[:] + p_p + [parent_key], message))
79                 if parent_key == 'Version':
80                     if parent_value not in valid_versions:
81                         message = 'IAM Policy Version needs to be one of (%s).' % (
82                             ', '.join(map(str, ['2012-10-17', '2008-10-17'])))
83                         matches.append(
84                             RuleMatch(p_p + [parent_key], message))
85                 if parent_key == 'Statement':
86                     if isinstance(parent_value, list):
87                         for i_s_v, i_s_p in parent_value.items_safe(p_p + ['Statement'], (dict)):
88                             matches.extend(
89                                 self._check_policy_statement(
90                                     i_s_p, i_s_v, is_identity_policy, resource_exceptions
91                                 )
92                             )
93                     elif isinstance(parent_value, dict):
94                         for i_s_v, i_s_p in parent_value.items_safe(p_p + ['Statement']):
95                             matches.extend(
96                                 self._check_policy_statement(
97                                     i_s_p, i_s_v, is_identity_policy, resource_exceptions
98                                 )
99                             )
100                     else:
101                         message = 'IAM Policy statement should be of list.'
102                         matches.append(
103                             RuleMatch(p_p + [parent_key], message))
104         return matches
105 
106     def _check_policy_statement(self, branch, statement, is_identity_policy, resource_exceptions):
107         """Check statements"""
108         matches = []
109         statement_valid_keys = [
110             'Action',
111             'Condition',
112             'Effect',
113             'NotAction',
114             'NotPrincipal',
115             'NotResource',
116             'Principal',
117             'Resource',
118             'Sid',
119         ]
120 
121         for key, _ in statement.items():
122             if key not in statement_valid_keys:
123                 message = 'IAM Policy statement key %s isn\'t valid' % (key)
124                 matches.append(
125                     RuleMatch(branch[:] + [key], message))
126         if 'Effect' not in statement:
127             message = 'IAM Policy statement missing Effect'
128             matches.append(
129                 RuleMatch(branch[:], message))
130         else:
131             for effect, effect_path in statement.get_safe('Effect'):
132                 if isinstance(effect, six.string_types):
133                     if effect not in ['Allow', 'Deny']:
134                         message = 'IAM Policy Effect should be Allow or Deny'
135                         matches.append(
136                             RuleMatch(branch[:] + effect_path, message))
137         if 'Action' not in statement and 'NotAction' not in statement:
138             message = 'IAM Policy statement missing Action or NotAction'
139             matches.append(
140                 RuleMatch(branch[:], message))
141         if is_identity_policy:
142             if 'Principal' in statement or 'NotPrincipal' in statement:
143                 message = 'IAM Resource Policy statement shouldn\'t have Principal or NotPrincipal'
144                 matches.append(
145                     RuleMatch(branch[:], message))
146         else:
147             if 'Principal' not in statement and 'NotPrincipal' not in statement:
148                 message = 'IAM Resource Policy statement should have Principal or NotPrincipal'
149                 matches.append(
150                     RuleMatch(branch[:] + ['Principal'], message))
151         if not resource_exceptions:
152             if 'Resource' not in statement and 'NotResource' not in statement:
153                 message = 'IAM Policy statement missing Resource or NotResource'
154                 matches.append(
155                     RuleMatch(branch[:], message))
156 
157         return(matches)
158 
159     def match_resource_properties(self, properties, resourcetype, path, cfn):
160         """Check CloudFormation Properties"""
161         matches = []
162 
163         is_identity_policy = True
164         if resourcetype in self.resources_and_keys:
165             is_identity_policy = False
166 
167         key = None
168         if resourcetype in self.resources_and_keys:
169             key = self.resources_and_keys.get(resourcetype)
170         else:
171             key = self.idp_and_keys.get(resourcetype)
172 
173         if not key:
174             # Key isn't defined return nothing
175             return matches
176 
177         resource_exceptions = False
178         if key == self.resource_exceptions.get(resourcetype):
179             resource_exceptions = True
180 
181         other_keys = []
182         for key, value in self.resources_and_keys.items():
183             if value != 'Policies':
184                 other_keys.append(key)
185         for key, value in self.idp_and_keys.items():
186             if value != 'Policies':
187                 other_keys.append(key)
188 
189         for key, value in properties.items():
190             if key == 'Policies' and isinstance(value, list):
191                 for index, policy in enumerate(properties.get(key, [])):
192                     matches.extend(
193                         cfn.check_value(
194                             obj=policy, key='PolicyDocument',
195                             path=path[:] + ['Policies', index],
196                             check_value=self.check_policy_document,
197                             is_identity_policy=is_identity_policy,
198                             resource_exceptions=resource_exceptions,
199                             start_mark=key.start_mark, end_mark=key.end_mark,
200                         ))
201             elif key in ['KeyPolicy', 'PolicyDocument', 'RepositoryPolicyText', 'AccessPolicies', 'InlinePolicy']:
202                 matches.extend(
203                     cfn.check_value(
204                         obj=properties, key=key,
205                         path=path[:],
206                         check_value=self.check_policy_document,
207                         is_identity_policy=is_identity_policy,
208                         resource_exceptions=resource_exceptions,
209                         start_mark=key.start_mark, end_mark=key.end_mark,
210                     ))
211 
212         return matches
213
```
--- 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
@@ -5,7 +5,7 @@
 import json
 from datetime import date
 import six
-from cfnlint.helpers import convert_dict
+from cfnlint.helpers import convert_dict, FUNCTIONS_SINGLE
 from cfnlint.rules import CloudFormationLintRule
 from cfnlint.rules import RuleMatch
 
@@ -154,6 +154,23 @@
                 matches.append(
                     RuleMatch(branch[:], message))
 
+        resources = statement.get('Resource', [])
+        if isinstance(resources, six.string_types):
+            resources = [resources]
+
+        for index, resource in enumerate(resources):
+            if isinstance(resource, dict):
+                if len(resource) == 1:
+                    for k in resource.keys():
+                        if k not in FUNCTIONS_SINGLE:
+                            message = 'IAM Policy statement Resource incorrectly formatted'
+                            matches.append(
+                                RuleMatch(branch[:] + ['Resource', index], message))
+                else:
+                    message = 'IAM Policy statement Resource incorrectly formatted'
+                    matches.append(
+                        RuleMatch(branch[:] + ['Resource', index], message))
+
         return(matches)
 
     def match_resource_properties(self, properties, resourcetype, path, cfn):

{"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@@ -5,7 +5,7 @@\n import json\n from datetime import date\n import six\n-from cfnlint.helpers import convert_dict\n+from cfnlint.helpers import convert_dict, FUNCTIONS_SINGLE\n from cfnlint.rules import CloudFormationLintRule\n from cfnlint.rules import RuleMatch\n \n@@ -154,6 +154,23 @@\n                 matches.append(\n                     RuleMatch(branch[:], message))\n \n+        resources = statement.get('Resource', [])\n+        if isinstance(resources, six.string_types):\n+            resources = [resources]\n+\n+        for index, resource in enumerate(resources):\n+            if isinstance(resource, dict):\n+                if len(resource) == 1:\n+                    for k in resource.keys():\n+                        if k not in FUNCTIONS_SINGLE:\n+                            message = 'IAM Policy statement Resource incorrectly formatted'\n+                            matches.append(\n+                                RuleMatch(branch[:] + ['Resource', index], message))\n+                else:\n+                    message = 'IAM Policy statement Resource incorrectly formatted'\n+                    matches.append(\n+                        RuleMatch(branch[:] + ['Resource', index], message))\n+\n         return(matches)\n \n     def match_resource_properties(self, properties, resourcetype, path, cfn):\n", "issue": "Linter does not catch MalformedPolicyDocument syntax error\n*cfn-lint version: 0.25.0*\r\n\r\n*Description of issue.*\r\n`cfn-lint` fails to catch the following syntax error pre-deployment, which is caught by CloudFormation during deployment:\r\n```\r\nSyntax errors in policy. (Service: AmazonIdentityManagement; Status Code: 400; Error Code: MalformedPolicyDocument; Request ID: ccd05e19-aa60-4b26-bfcc-8fbafca40c61; Proxy: null)\r\n```\r\n\r\nThe policy in question has the following template:\r\n```yaml\r\nAPILambdaExecutionPolicy:\r\n    Type: AWS::IAM::ManagedPolicy\r\n    Properties:\r\n      Description: Allows API Gateway triggered Lambdas access to service resources\r\n      Path: /\r\n      ManagedPolicyName:\r\n        Fn::Sub: ${Project}-APILambdaExecutionPolicy-${AWS::Region}\r\n      PolicyDocument:\r\n        Version: '2012-10-17'\r\n        Statement:\r\n        - Effect: Allow\r\n          Action:\r\n          - dynamodb:*Get*\r\n          - dynamodb:*Item*\r\n          - dynamodb:Describe*\r\n          - dynamodb:Query\r\n          Resource:\r\n          - Fn::GetAtt:\r\n            - DynamoDB\r\n            - Outputs.TaskDBTableARN\r\n          - Fn::Sub: ${DynamoDB.Outputs.TaskDBTableARN}/*\r\n          - Effect: Allow\r\n            Action:\r\n            - cloudwatch:PutMetricAlarm\r\n            - cloudwatch:PutMetricData\r\n            Resource:\r\n            - '*'\r\n          - Effect: Allow\r\n            Action:\r\n            - xray:PutTraceSegments\r\n            - xray:PutTelemetryRecords\r\n            Resource:\r\n            - '*'\r\n          - Effect: Allow\r\n            Action:\r\n            - sqs:GetQueueUrl\r\n            - sqs:SendMessage\r\n            Resource:\r\n            - Fn::GetAtt:\r\n              - SQS\r\n              - Outputs.QueueARN\r\n          - Effect: Allow\r\n            Action:\r\n            - sns:Publish\r\n            Resource:\r\n            - arn:aws:sns:*:*:*  \r\n```\r\n\r\n\r\n\n", "before_files": [{"content": "\"\"\"\nCopyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\nSPDX-License-Identifier: MIT-0\n\"\"\"\nimport json\nfrom datetime import date\nimport six\nfrom cfnlint.helpers import convert_dict\nfrom cfnlint.rules import CloudFormationLintRule\nfrom cfnlint.rules 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 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        super(Policy, self).__init__()\n        self.resource_exceptions = {\n            'AWS::ECR::Repository': 'RepositoryPolicyText',\n        }\n        self.resources_and_keys = {\n            'AWS::ECR::Repository': 'RepositoryPolicyText',\n            'AWS::Elasticsearch::Domain': 'AccessPolicies',\n            'AWS::KMS::Key': 'KeyPolicy',\n            'AWS::S3::BucketPolicy': 'PolicyDocument',\n            'AWS::SNS::TopicPolicy': 'PolicyDocument',\n            'AWS::SQS::QueuePolicy': 'PolicyDocument',\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            'AWS::SSO::PermissionSet': 'InlinePolicy',\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, is_identity_policy, resource_exceptions, start_mark, end_mark):\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 isinstance(value, six.string_types):\n            try:\n                value = convert_dict(json.loads(value), start_mark, end_mark)\n            except Exception as ex:  # pylint: disable=W0703,W0612\n                message = 'IAM Policy Documents need to be JSON'\n                matches.append(RuleMatch(path[:], message))\n                return matches\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 p_vs, p_p in value.items_safe(path[:], (dict)):\n            for parent_key, parent_value in p_vs.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[:] + p_p + [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(p_p + [parent_key], message))\n                if parent_key == 'Statement':\n                    if isinstance(parent_value, list):\n                        for i_s_v, i_s_p in parent_value.items_safe(p_p + ['Statement'], (dict)):\n                            matches.extend(\n                                self._check_policy_statement(\n                                    i_s_p, i_s_v, is_identity_policy, resource_exceptions\n                                )\n                            )\n                    elif isinstance(parent_value, dict):\n                        for i_s_v, i_s_p in parent_value.items_safe(p_p + ['Statement']):\n                            matches.extend(\n                                self._check_policy_statement(\n                                    i_s_p, i_s_v, is_identity_policy, resource_exceptions\n                                )\n                            )\n                    else:\n                        message = 'IAM Policy statement should be of list.'\n                        matches.append(\n                            RuleMatch(p_p + [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            'Action',\n            'Condition',\n            'Effect',\n            'NotAction',\n            'NotPrincipal',\n            'NotResource',\n            'Principal',\n            'Resource',\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            for effect, effect_path in statement.get_safe('Effect'):\n                if isinstance(effect, six.string_types):\n                    if effect not in ['Allow', 'Deny']:\n                        message = 'IAM Policy Effect should be Allow or Deny'\n                        matches.append(\n                            RuleMatch(branch[:] + effect_path, 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        other_keys = []\n        for key, value in self.resources_and_keys.items():\n            if value != 'Policies':\n                other_keys.append(key)\n        for key, value in self.idp_and_keys.items():\n            if value != 'Policies':\n                other_keys.append(key)\n\n        for key, value in properties.items():\n            if key == 'Policies' and isinstance(value, list):\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                            is_identity_policy=is_identity_policy,\n                            resource_exceptions=resource_exceptions,\n                            start_mark=key.start_mark, end_mark=key.end_mark,\n                        ))\n            elif key in ['KeyPolicy', 'PolicyDocument', 'RepositoryPolicyText', 'AccessPolicies', 'InlinePolicy']:\n                matches.extend(\n                    cfn.check_value(\n                        obj=properties, key=key,\n                        path=path[:],\n                        check_value=self.check_policy_document,\n                        is_identity_policy=is_identity_policy,\n                        resource_exceptions=resource_exceptions,\n                        start_mark=key.start_mark, end_mark=key.end_mark,\n                    ))\n\n        return matches\n", "path": "src/cfnlint/rules/resources/iam/Policy.py"}], "after_files": [{"content": "\"\"\"\nCopyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\nSPDX-License-Identifier: MIT-0\n\"\"\"\nimport json\nfrom datetime import date\nimport six\nfrom cfnlint.helpers import convert_dict, FUNCTIONS_SINGLE\nfrom cfnlint.rules import CloudFormationLintRule\nfrom cfnlint.rules 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 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        super(Policy, self).__init__()\n        self.resource_exceptions = {\n            'AWS::ECR::Repository': 'RepositoryPolicyText',\n        }\n        self.resources_and_keys = {\n            'AWS::ECR::Repository': 'RepositoryPolicyText',\n            'AWS::Elasticsearch::Domain': 'AccessPolicies',\n            'AWS::KMS::Key': 'KeyPolicy',\n            'AWS::S3::BucketPolicy': 'PolicyDocument',\n            'AWS::SNS::TopicPolicy': 'PolicyDocument',\n            'AWS::SQS::QueuePolicy': 'PolicyDocument',\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            'AWS::SSO::PermissionSet': 'InlinePolicy',\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, is_identity_policy, resource_exceptions, start_mark, end_mark):\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 isinstance(value, six.string_types):\n            try:\n                value = convert_dict(json.loads(value), start_mark, end_mark)\n            except Exception as ex:  # pylint: disable=W0703,W0612\n                message = 'IAM Policy Documents need to be JSON'\n                matches.append(RuleMatch(path[:], message))\n                return matches\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 p_vs, p_p in value.items_safe(path[:], (dict)):\n            for parent_key, parent_value in p_vs.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[:] + p_p + [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(p_p + [parent_key], message))\n                if parent_key == 'Statement':\n                    if isinstance(parent_value, list):\n                        for i_s_v, i_s_p in parent_value.items_safe(p_p + ['Statement'], (dict)):\n                            matches.extend(\n                                self._check_policy_statement(\n                                    i_s_p, i_s_v, is_identity_policy, resource_exceptions\n                                )\n                            )\n                    elif isinstance(parent_value, dict):\n                        for i_s_v, i_s_p in parent_value.items_safe(p_p + ['Statement']):\n                            matches.extend(\n                                self._check_policy_statement(\n                                    i_s_p, i_s_v, is_identity_policy, resource_exceptions\n                                )\n                            )\n                    else:\n                        message = 'IAM Policy statement should be of list.'\n                        matches.append(\n                            RuleMatch(p_p + [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            'Action',\n            'Condition',\n            'Effect',\n            'NotAction',\n            'NotPrincipal',\n            'NotResource',\n            'Principal',\n            'Resource',\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            for effect, effect_path in statement.get_safe('Effect'):\n                if isinstance(effect, six.string_types):\n                    if effect not in ['Allow', 'Deny']:\n                        message = 'IAM Policy Effect should be Allow or Deny'\n                        matches.append(\n                            RuleMatch(branch[:] + effect_path, 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        resources = statement.get('Resource', [])\n        if isinstance(resources, six.string_types):\n            resources = [resources]\n\n        for index, resource in enumerate(resources):\n            if isinstance(resource, dict):\n                if len(resource) == 1:\n                    for k in resource.keys():\n                        if k not in FUNCTIONS_SINGLE:\n                            message = 'IAM Policy statement Resource incorrectly formatted'\n                            matches.append(\n                                RuleMatch(branch[:] + ['Resource', index], message))\n                else:\n                    message = 'IAM Policy statement Resource incorrectly formatted'\n                    matches.append(\n                        RuleMatch(branch[:] + ['Resource', index], 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        other_keys = []\n        for key, value in self.resources_and_keys.items():\n            if value != 'Policies':\n                other_keys.append(key)\n        for key, value in self.idp_and_keys.items():\n            if value != 'Policies':\n                other_keys.append(key)\n\n        for key, value in properties.items():\n            if key == 'Policies' and isinstance(value, list):\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                            is_identity_policy=is_identity_policy,\n                            resource_exceptions=resource_exceptions,\n                            start_mark=key.start_mark, end_mark=key.end_mark,\n                        ))\n            elif key in ['KeyPolicy', 'PolicyDocument', 'RepositoryPolicyText', 'AccessPolicies', 'InlinePolicy']:\n                matches.extend(\n                    cfn.check_value(\n                        obj=properties, key=key,\n                        path=path[:],\n                        check_value=self.check_policy_document,\n                        is_identity_policy=is_identity_policy,\n                        resource_exceptions=resource_exceptions,\n                        start_mark=key.start_mark, end_mark=key.end_mark,\n                    ))\n\n        return matches\n", "path": "src/cfnlint/rules/resources/iam/Policy.py"}]}