Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
14 changes: 10 additions & 4 deletions samtranslator/model/api/api_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
from samtranslator.model.intrinsics import is_instrinsic

_CORS_WILDCARD = "'*'"
CorsProperties = namedtuple("_CorsProperties", ["AllowMethods", "AllowHeaders", "AllowOrigin", "MaxAge"])
# Default the Cors Properties to '*' wildcard. Other properties are actually Optional
CorsProperties.__new__.__defaults__ = (None, None, _CORS_WILDCARD, None)
CorsProperties = namedtuple("_CorsProperties", ["AllowMethods", "AllowHeaders", "AllowOrigin", "MaxAge", "ExposeHeaders", "AllowCredentials"])
# Default the Cors Properties to '*' wildcard and False AllowCredentials. Other properties are actually Optional
CorsProperties.__new__.__defaults__ = (None, None, _CORS_WILDCARD, None, None, False)

class ApiGenerator(object):

Expand Down Expand Up @@ -203,10 +203,16 @@ def _add_cors(self):
raise InvalidResourceException(self.logical_id, "Unable to add Cors configuration because "
"'DefinitionBody' does not contain a valid Swagger")

if properties.AllowCredentials is True and properties.AllowOrigin == _CORS_WILDCARD:
raise InvalidResourceException(self.logical_id, "Unable to add Cors configuration because "
"'AllowCredentials' can not be true when "
"'AllowOrigin' is \"'*'\" or not set")

editor = SwaggerEditor(self.definition_body)
for path in editor.iter_on_path():
editor.add_cors(path, properties.AllowOrigin, properties.AllowHeaders, properties.AllowMethods,
max_age=properties.MaxAge)
max_age=properties.MaxAge, exposed_headers=properties.ExposeHeaders,
allow_credentials=properties.AllowCredentials)

# Assign the Swagger back to template
self.definition_body = editor.swagger
Expand Down
29 changes: 26 additions & 3 deletions samtranslator/swagger/swagger.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class SwaggerEditor(object):
_X_APIGW_INTEGRATION = 'x-amazon-apigateway-integration'
_X_ANY_METHOD = 'x-amazon-apigateway-any-method'


def __init__(self, doc):
"""
Initialize the class with a swagger dictionary. This class creates a copy of the Swagger and performs all
Expand Down Expand Up @@ -117,7 +118,8 @@ def iter_on_path(self):
for path, value in self.paths.items():
yield path

def add_cors(self, path, allowed_origins, allowed_headers=None, allowed_methods=None, max_age=None):
def add_cors(self, path, allowed_origins, allowed_headers=None, allowed_methods=None, max_age=None,
exposed_headers=None, allow_credentials=None):
"""
Add CORS configuration to this path. Specifically, we will add a OPTIONS response config to the Swagger that
will return headers required for CORS. Since SAM uses aws_proxy integration, we cannot inject the headers
Expand All @@ -138,6 +140,9 @@ def add_cors(self, path, allowed_origins, allowed_headers=None, allowed_methods=
Value can also be an intrinsic function dict.
:param integer/dict max_age: Maximum duration to cache the CORS Preflight request. Value is set on
Access-Control-Max-Age header. Value can also be an intrinsic function dict.
:param string/dict exposed_headers: Comma separated list of exposed headers.
Value can also be an instrinsic function dict.
:param bool/None allow_credentials: Flags whether request is allowed to contain credentials.
:raises ValueError: When values for one of the allowed_* variables is empty
"""

Expand All @@ -155,15 +160,20 @@ def add_cors(self, path, allowed_origins, allowed_headers=None, allowed_methods=
# APIGW expects the value to be a "string expression". Hence wrap in another quote. Ex: "'GET,POST,DELETE'"
allowed_methods = "'{}'".format(allowed_methods)

if allow_credentials is not True:
allow_credentials = False

# Add the Options method and the CORS response
self.add_path(path, self._OPTIONS_METHOD)
self.paths[path][self._OPTIONS_METHOD] = self._options_method_response_for_cors(allowed_origins,
allowed_headers,
allowed_methods,
max_age)
max_age,
exposed_headers,
allow_credentials)

def _options_method_response_for_cors(self, allowed_origins, allowed_headers=None, allowed_methods=None,
max_age=None):
max_age=None, exposed_headers=None, allow_credentials=None):
"""
Returns a Swagger snippet containing configuration for OPTIONS HTTP Method to configure CORS.

Expand All @@ -178,6 +188,9 @@ def _options_method_response_for_cors(self, allowed_origins, allowed_headers=Non
Value can also be an intrinsic function dict.
:param integer/dict max_age: Maximum duration to cache the CORS Preflight request. Value is set on
Access-Control-Max-Age header. Value can also be an intrinsic function dict.
:param string/dict exposed_headers: Comma separated list of exposed headers.
Value can also be an instrinsic function dict.
:param bool allow_credentials: Flags whether request is allowed to contain credentials.

:return dict: Dictionary containing Options method configuration for CORS
"""
Expand All @@ -186,6 +199,8 @@ def _options_method_response_for_cors(self, allowed_origins, allowed_headers=Non
ALLOW_HEADERS = "Access-Control-Allow-Headers"
ALLOW_METHODS = "Access-Control-Allow-Methods"
MAX_AGE = "Access-Control-Max-Age"
EXPOSE_HEADERS = "Access-Control-Expose-Headers"
ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials"
HEADER_RESPONSE = lambda x: "method.response.header."+x

response_parameters = {
Expand Down Expand Up @@ -216,6 +231,14 @@ def _options_method_response_for_cors(self, allowed_origins, allowed_headers=Non
# MaxAge can be set to 0, which is a valid value. So explicitly check against None
response_parameters[HEADER_RESPONSE(MAX_AGE)] = max_age
response_headers[MAX_AGE] = {"type": "integer"}
if exposed_headers:
response_parameters[HEADER_RESPONSE(EXPOSE_HEADERS)] = exposed_headers
response_headers[EXPOSE_HEADERS] = {"type": "string"}
if allow_credentials is True:
# Allow-Credentials only has a valid value of true, it should be omitted otherwise.
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
response_parameters[HEADER_RESPONSE(ALLOW_CREDENTIALS)] = "'true'"
response_headers[ALLOW_CREDENTIALS] = {"type": "string"}

return {
"summary": "CORS support",
Expand Down
Loading