Skip to content

Commit c8e4da9

Browse files
authored
fix: handle non-dict DefinitionBody path item in _openapi_postprocess (#2216)
* handle null DefinitionBody path item and null options field value, plus some reformatting * check dictionary type * more refactoring + throw error if not dict
1 parent 1174f0f commit c8e4da9

6 files changed

+80
-56
lines changed

samtranslator/model/api/api_generator.py

Lines changed: 17 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -969,41 +969,30 @@ def _openapi_postprocess(self, definition_body):
969969
del definition_body["definitions"]
970970
# removes `consumes` and `produces` options for CORS in openapi3 and
971971
# adds `schema` for the headers in responses for openapi3
972-
if definition_body.get("paths"):
973-
for path in definition_body.get("paths"):
974-
if definition_body.get("paths").get(path).get("options"):
975-
definition_body_options = definition_body.get("paths").get(path).get("options").copy()
976-
for field in definition_body_options.keys():
972+
paths = definition_body.get("paths")
973+
if paths:
974+
for path, path_item in paths.items():
975+
SwaggerEditor.validate_path_item_is_dict(path_item, path)
976+
if path_item.get("options"):
977+
options = path_item.get("options").copy()
978+
for field, field_val in options.items():
977979
# remove unsupported produces and consumes in options for openapi3
978980
if field in ["produces", "consumes"]:
979981
del definition_body["paths"][path]["options"][field]
980982
# add schema for the headers in options section for openapi3
981983
if field in ["responses"]:
982-
options_path = definition_body["paths"][path]["options"]
983-
if options_path and not isinstance(options_path.get(field), dict):
984-
raise InvalidDocumentException(
985-
[
986-
InvalidTemplateException(
987-
"Value of responses in options method for path {} must be a "
988-
"dictionary according to Swagger spec.".format(path)
989-
)
990-
]
991-
)
992-
if (
993-
options_path
994-
and options_path.get(field).get("200")
995-
and options_path.get(field).get("200").get("headers")
996-
):
997-
headers = definition_body["paths"][path]["options"][field]["200"]["headers"]
998-
for header in headers.keys():
999-
header_value = {
1000-
"schema": definition_body["paths"][path]["options"][field]["200"][
1001-
"headers"
1002-
][header]
1003-
}
984+
SwaggerEditor.validate_is_dict(
985+
field_val,
986+
"Value of responses in options method for path {} must be a "
987+
"dictionary according to Swagger spec.".format(path),
988+
)
989+
if field_val.get("200") and field_val.get("200").get("headers"):
990+
headers = field_val["200"]["headers"]
991+
for header, header_val in headers.items():
992+
new_header_val_with_schema = {"schema": header_val}
1004993
definition_body["paths"][path]["options"][field]["200"]["headers"][
1005994
header
1006-
] = header_value
995+
] = new_header_val_with_schema
1007996

1008997
return definition_body
1009998

samtranslator/swagger/swagger.py

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -139,16 +139,7 @@ def add_path(self, path, method=None):
139139
method = self._normalize_method_name(method)
140140

141141
path_dict = self.paths.setdefault(path, {})
142-
143-
if not isinstance(path_dict, dict):
144-
# Either customers has provided us an invalid Swagger, or this class has messed it somehow
145-
raise InvalidDocumentException(
146-
[
147-
InvalidTemplateException(
148-
"Value of '{}' path must be a dictionary according to Swagger spec.".format(path)
149-
)
150-
]
151-
)
142+
SwaggerEditor.validate_path_item_is_dict(path_dict, path)
152143

153144
if self._CONDITIONAL_IF in path_dict:
154145
path_dict = path_dict[self._CONDITIONAL_IF][1]
@@ -536,15 +527,10 @@ def set_path_default_authorizer(
536527
# It is possible that the method could have two definitions in a Fn::If block.
537528
for method_definition in self.get_method_contents(method):
538529

530+
SwaggerEditor.validate_is_dict(
531+
method_definition, "{} for path {} is not a valid dictionary.".format(method_definition, path)
532+
)
539533
# If no integration given, then we don't need to process this definition (could be AWS::NoValue)
540-
if not isinstance(method_definition, dict):
541-
raise InvalidDocumentException(
542-
[
543-
InvalidTemplateException(
544-
"{} for path {} is not a valid dictionary.".format(method_definition, path)
545-
)
546-
]
547-
)
548534
if not self.method_definition_has_integration(method_definition):
549535
continue
550536
existing_security = method_definition.get("security", [])
@@ -559,14 +545,9 @@ def set_path_default_authorizer(
559545
# (e.g. sigv4 (AWS_IAM), api_key (API Key/Usage Plans), NONE (marker for ignoring default))
560546
# We want to ensure only a single Authorizer security entry exists while keeping everything else
561547
for security in existing_security:
562-
if not isinstance(security, dict):
563-
raise InvalidDocumentException(
564-
[
565-
InvalidTemplateException(
566-
"{} in Security for path {} is not a valid dictionary.".format(security, path)
567-
)
568-
]
569-
)
548+
SwaggerEditor.validate_is_dict(
549+
security, "{} in Security for path {} is not a valid dictionary.".format(security, path)
550+
)
570551
if authorizer_names.isdisjoint(security.keys()):
571552
existing_non_authorizer_security.append(security)
572553
else:
@@ -912,8 +893,7 @@ def add_resource_policy(self, resource_policy, path, stage):
912893
"""
913894
if resource_policy is None:
914895
return
915-
if not isinstance(resource_policy, dict):
916-
raise InvalidDocumentException([InvalidTemplateException("Resource Policy is not a valid dictionary.")])
896+
SwaggerEditor.validate_is_dict(resource_policy, "Resource Policy is not a valid dictionary.")
917897

918898
aws_account_whitelist = resource_policy.get("AwsAccountWhitelist")
919899
aws_account_blacklist = resource_policy.get("AwsAccountBlacklist")
@@ -1244,6 +1224,31 @@ def is_valid(data):
12441224
)
12451225
return False
12461226

1227+
@staticmethod
1228+
def validate_is_dict(obj, exception_message):
1229+
"""
1230+
Throws exception if obj is not a dict
1231+
1232+
:param obj: object being validated
1233+
:param exception_message: message to include in exception if obj is not a dict
1234+
"""
1235+
1236+
if not isinstance(obj, dict):
1237+
raise InvalidDocumentException([InvalidTemplateException(exception_message)])
1238+
1239+
@staticmethod
1240+
def validate_path_item_is_dict(path_item, path):
1241+
"""
1242+
Throws exception if path_item is not a dict
1243+
1244+
:param path_item: path_item (value at the path) being validated
1245+
:param path: path name
1246+
"""
1247+
1248+
SwaggerEditor.validate_is_dict(
1249+
path_item, "Value of '{}' path must be a dictionary according to Swagger spec.".format(path)
1250+
)
1251+
12471252
@staticmethod
12481253
def gen_skeleton():
12491254
"""
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Resources:
2+
ApiWithInvalidPath:
3+
Type: AWS::Serverless::Api
4+
Properties:
5+
StageName: Prod
6+
OpenApiVersion: 3.0.1
7+
DefinitionBody:
8+
openapi: 3.0.1
9+
info:
10+
title: test invalid paths Api
11+
paths:
12+
/foo: null
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Resources:
2+
ApiWithInvalidPath:
3+
Type: AWS::Serverless::Api
4+
Properties:
5+
StageName: Prod
6+
OpenApiVersion: 3.0.1
7+
DefinitionBody:
8+
openapi: 3.0.1
9+
info:
10+
title: test invalid paths Api
11+
paths:
12+
/foo: invalid
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Structure of the SAM template is invalid. Value of '/foo' path must be a dictionary according to Swagger spec."
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Structure of the SAM template is invalid. Value of '/foo' path must be a dictionary according to Swagger spec."
3+
}

0 commit comments

Comments
 (0)