Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions samtranslator/model/eventsources/push.py
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,35 @@ def _add_swagger_integration(self, api, function, intrinsics_resolver):
path=self.Path, method_name=self.Method, request_model=self.RequestModel
)

validate_body = self.RequestModel.get("ValidateBody")
validate_parameters = self.RequestModel.get("ValidateParameters")

# Checking if any of the fields are defined as it can be false we are checking if the field are not None
if validate_body is not None or validate_parameters is not None:

# as we are setting two different fields we are here setting as default False
# In case one of them are not defined
validate_body = False if validate_body is None else validate_body
validate_parameters = False if validate_parameters is None else validate_parameters

# If not type None but any other type it should explicitly invalidate the Spec
# Those fields should be only a boolean
if not isinstance(validate_body, bool) or not isinstance(validate_parameters, bool):
raise InvalidEventException(
self.relative_id,
"Unable to set Validator to RequestModel [{model}] on API method [{method}] for path [{path}] "
"ValidateBody and ValidateParameters must be a boolean type, strings or intrinsics are not supported.".format(
model=method_model, method=self.Method, path=self.Path
),
)

editor.add_request_validator_to_method(
path=self.Path,
method_name=self.Method,
validate_body=validate_body,
validate_parameters=validate_parameters,
)

if self.RequestParameters:

default_value = {"Required": False, "Caching": False}
Expand Down
62 changes: 62 additions & 0 deletions samtranslator/swagger/swagger.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class SwaggerEditor(object):
_X_APIGW_GATEWAY_RESPONSES = "x-amazon-apigateway-gateway-responses"
_X_APIGW_POLICY = "x-amazon-apigateway-policy"
_X_ANY_METHOD = "x-amazon-apigateway-any-method"
_X_APIGW_REQUEST_VALIDATORS = "x-amazon-apigateway-request-validators"
_X_APIGW_REQUEST_VALIDATOR = "x-amazon-apigateway-request-validator"
_CACHE_KEY_PARAMETERS = "cacheKeyParameters"
# https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
_ALL_HTTP_METHODS = ["OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "PATCH"]
Expand Down Expand Up @@ -781,6 +783,46 @@ def _set_method_apikey_handling(self, path, method_name, apikey_required):
if security != existing_security:
method_definition["security"] = security

def add_request_validator_to_method(self, path, method_name, validate_body=False, validate_parameters=False):
"""
Adds request model body parameter for this path/method.

:param string path: Path name
:param string method_name: Method name
:param bool validate_body: Add validator parameter on the body
:param bool validate_parameters: Validate request
"""

normalized_method_name = self._normalize_method_name(method_name)
validator_name = SwaggerEditor.get_validator_name(validate_body, validate_parameters)

# Creating validator
request_validator_definition = {
validator_name: {"validateRequestBody": validate_body, "validateRequestParameters": validate_parameters}
}
if not self._doc.get(self._X_APIGW_REQUEST_VALIDATORS):
self._doc[self._X_APIGW_REQUEST_VALIDATORS] = {}

if not self._doc[self._X_APIGW_REQUEST_VALIDATORS].get(validator_name):
# Adding only if the validator hasn't been defined already
self._doc[self._X_APIGW_REQUEST_VALIDATORS].update(request_validator_definition)

# It is possible that the method could have two definitions in a Fn::If block.
for path_method_name, method in self.get_path(path).items():
normalized_path_method_name = self._normalize_method_name(path_method_name)

# Adding it to only given method to the path
if normalized_path_method_name == normalized_method_name:
for method_definition in self.get_method_contents(method):

# If no integration given, then we don't need to process this definition (could be AWS::NoValue)
if not self.method_definition_has_integration(method_definition):
continue

set_validator_to_method = {self._X_APIGW_REQUEST_VALIDATOR: validator_name}
# Setting validator to the given method
method_definition.update(set_validator_to_method)

def add_request_model_to_method(self, path, method_name, request_model):
"""
Adds request model body parameter for this path/method.
Expand Down Expand Up @@ -1265,6 +1307,26 @@ def get_path_without_trailing_slash(path):
# convert greedy paths to such as {greedy+}, {proxy+} to "*"
return re.sub(r"{([a-zA-Z0-9._-]+|[a-zA-Z0-9._-]+\+|proxy\+)}", "*", path)

@staticmethod
def get_validator_name(validate_body, validate_parameters):
"""
Get a readable path name to use as validator name

:param boolean validate_body: Boolean if validate body
:param boolean validate_request: Boolean if validate request
:return string: Normalized validator name
"""
if validate_body and validate_parameters:
return "body-and-params"

if validate_body and not validate_parameters:
return "body-only"

if not validate_body and validate_parameters:
return "params-only"

return "no-validation"

@staticmethod
def _validate_list_property_is_resolved(property_list):
"""
Expand Down
112 changes: 112 additions & 0 deletions tests/swagger/test_swagger.py
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,118 @@ def test_must_add_body_parameter_to_method_openapi_required_true(self):
self.assertEqual(expected, editor.swagger["paths"]["/foo"]["get"]["requestBody"])


class TestSwaggerEditor_add_request_validator_to_method(TestCase):
def setUp(self):

self.original_swagger = {
"swagger": "2.0",
"paths": {
"/foo": {
"get": {
"x-amazon-apigateway-integration": {"test": "must have integration"},
"parameters": [{"test": "existing parameter"}],
}
}
},
}

self.editor = SwaggerEditor(self.original_swagger)

def test_must_add_validator_parameters_to_method_with_validators_true(self):

self.editor.add_request_validator_to_method("/foo", "get", True, True)
expected = {"body-and-params": {"validateRequestBody": True, "validateRequestParameters": True}}

self.assertEqual(expected, self.editor.swagger["x-amazon-apigateway-request-validators"])
self.assertEqual(
"body-and-params", self.editor.swagger["paths"]["/foo"]["get"]["x-amazon-apigateway-request-validator"]
)

def test_must_add_validator_parameters_to_method_with_validators_false(self):

self.editor.add_request_validator_to_method("/foo", "get", False, False)

expected = {"no-validation": {"validateRequestBody": False, "validateRequestParameters": False}}

self.assertEqual(expected, self.editor.swagger["x-amazon-apigateway-request-validators"])
self.assertEqual(
"no-validation", self.editor.swagger["paths"]["/foo"]["get"]["x-amazon-apigateway-request-validator"]
)

def test_must_add_validator_parameters_to_method_with_validators_mixing(self):

self.editor.add_request_validator_to_method("/foo", "get", True, False)

expected = {"body-only": {"validateRequestBody": True, "validateRequestParameters": False}}

self.assertEqual(expected, self.editor.swagger["x-amazon-apigateway-request-validators"])
self.assertEqual(
"body-only", self.editor.swagger["paths"]["/foo"]["get"]["x-amazon-apigateway-request-validator"]
)

def test_must_add_validator_parameters_to_method_and_not_duplicate(self):
self.original_swagger["paths"].update(
{
"/bar": {
"get": {
"x-amazon-apigateway-integration": {"test": "must have integration"},
"parameters": [{"test": "existing parameter"}],
}
},
"/foo-bar": {
"get": {
"x-amazon-apigateway-integration": {"test": "must have integration"},
"parameters": [{"test": "existing parameter"}],
}
},
}
)

editor = SwaggerEditor(self.original_swagger)

editor.add_request_validator_to_method("/foo", "get", True, True)
editor.add_request_validator_to_method("/bar", "get", True, True)
editor.add_request_validator_to_method("/foo-bar", "get", True, True)

expected = {"body-and-params": {"validateRequestBody": True, "validateRequestParameters": True}}

self.assertEqual(expected, editor.swagger["x-amazon-apigateway-request-validators"])
self.assertEqual(
"body-and-params", editor.swagger["paths"]["/foo"]["get"]["x-amazon-apigateway-request-validator"]
)
self.assertEqual(
"body-and-params", editor.swagger["paths"]["/bar"]["get"]["x-amazon-apigateway-request-validator"]
)
self.assertEqual(
"body-and-params", editor.swagger["paths"]["/foo-bar"]["get"]["x-amazon-apigateway-request-validator"]
)

self.assertEqual(1, len(editor.swagger["x-amazon-apigateway-request-validators"].keys()))

@parameterized.expand(
[
param(True, False, "body-only"),
param(True, True, "body-and-params"),
param(False, True, "params-only"),
param(False, False, "no-validation"),
]
)
def test_must_return_validator_names(self, validate_body, validate_request, normalized_name):
normalized_validator_name_conversion = SwaggerEditor.get_validator_name(validate_body, validate_request)
self.assertEqual(normalized_validator_name_conversion, normalized_name)

def test_must_add_validator_parameters_to_method_with_validators_false_by_default(self):

self.editor.add_request_validator_to_method("/foo", "get")

expected = {"no-validation": {"validateRequestBody": False, "validateRequestParameters": False}}

self.assertEqual(expected, self.editor.swagger["x-amazon-apigateway-request-validators"])
self.assertEqual(
"no-validation", self.editor.swagger["paths"]["/foo"]["get"]["x-amazon-apigateway-request-validator"]
)


class TestSwaggerEditor_add_auth(TestCase):
def setUp(self):

Expand Down
157 changes: 157 additions & 0 deletions tests/translator/input/api_request_model_with_validator.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
Resources:
HtmlFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3://sam-demo-bucket/member_portal.zip
Handler: index.gethtml
Runtime: nodejs12.x
Events:
GetHtml:
Type: Api
Properties:
RestApiId: HtmlApi
Path: /
Method: get
RequestModel:
Model: User
Required: true
ValidateBody: true
ValidateParameters: true

HtmlFunctionNoValidation:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3://sam-demo-bucket/member_portal.zip
Handler: index.gethtml
Runtime: nodejs12.x
Events:
GetHtml:
Type: Api
Properties:
RestApiId: HtmlApi
Path: /no-validation
Method: get
RequestModel:
Model: User
Required: true
ValidateBody: false
ValidateParameters: false

HtmlFunctionMixinValidation:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3://sam-demo-bucket/member_portal.zip
Handler: index.gethtml
Runtime: nodejs12.x
Events:
GetHtml:
Type: Api
Properties:
RestApiId: HtmlApi
Path: /mixin
Method: get
RequestModel:
Model: User
Required: true
ValidateBody: true
ValidateParameters: false

HtmlFunctionOnlyBodyDefinition:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3://sam-demo-bucket/member_portal.zip
Handler: index.gethtml
Runtime: nodejs12.x
Events:
GetHtml:
Type: Api
Properties:
RestApiId: HtmlApi
Path: /only-body-true
Method: get
RequestModel:
Model: User
Required: true
ValidateBody: true

HtmlFunctionOnlyRequestDefinition:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3://sam-demo-bucket/member_portal.zip
Handler: index.gethtml
Runtime: nodejs12.x
Events:
GetHtml:
Type: Api
Properties:
RestApiId: HtmlApi
Path: /only-request-true
Method: get
RequestModel:
Model: User
Required: true
ValidateParameters: true

HtmlFunctionOnlyRequestDefinitionFalse:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3://sam-demo-bucket/member_portal.zip
Handler: index.gethtml
Runtime: nodejs12.x
Events:
GetHtml:
Type: Api
Properties:
RestApiId: HtmlApi
Path: /only-request-false
Method: get
RequestModel:
Model: User
Required: true
ValidateParameters: false

HtmlFunctionOnlyBodyDefinitionFalse:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3://sam-demo-bucket/member_portal.zip
Handler: index.gethtml
Runtime: nodejs12.x
Events:
GetHtml:
Type: Api
Properties:
RestApiId: HtmlApi
Path: /only-body-false
Method: get
RequestModel:
Model: User
Required: true
ValidateBody: false

HtmlFunctionNotDefinedValidation:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3://sam-demo-bucket/member_portal.zip
Handler: index.gethtml
Runtime: nodejs12.x
Events:
GetHtml:
Type: Api
Properties:
RestApiId: HtmlApi
Path: /not-defined
Method: get
RequestModel:
Model: User
Required: true

HtmlApi:
Type: AWS::Serverless::Api
Properties:
StageName: Prod
Models:
User:
type: object
properties:
username:
type: string
Loading