def test_empty_elements(self): cors = Cors(allow_origin="www.domain.com", allow_methods=",".join(["GET", "POST", "OPTIONS"])) headers = Cors.cors_to_headers(cors) self.assertEqual( headers, {"Access-Control-Allow-Origin": "www.domain.com", "Access-Control-Allow-Methods": "GET,POST,OPTIONS"}, )
def extract_cors_http( self, cors_prop: Union[bool, Dict], ) -> Optional[Cors]: """ Extract Cors property from AWS::Serverless::HttpApi resource by reading and parsing Swagger documents. The result is added to the HttpApi. Parameters ---------- cors_prop : dict Resource properties for CorsConfiguration """ cors = None if cors_prop and isinstance(cors_prop, dict): allow_methods = self._get_cors_prop_http(cors_prop, "AllowMethods", list) if isinstance(allow_methods, list): allow_methods = CfnBaseApiProvider.normalize_cors_allow_methods( allow_methods) else: allow_methods = ",".join(sorted(Route.ANY_HTTP_METHODS)) allow_origins = self._get_cors_prop_http(cors_prop, "AllowOrigins", list) if isinstance(allow_origins, list): allow_origins = ",".join(allow_origins) allow_headers = self._get_cors_prop_http(cors_prop, "AllowHeaders", list) if isinstance(allow_headers, list): allow_headers = ",".join(allow_headers) # Read AllowCredentials but only output the header with the case-sensitive value of true # (see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials) allow_credentials = "true" if self._get_cors_prop_http( cors_prop, "AllowCredentials", bool) else None max_age = self._get_cors_prop_http(cors_prop, "MaxAge", int) cors = Cors( allow_origin=allow_origins, allow_methods=allow_methods, allow_headers=allow_headers, allow_credentials=allow_credentials, max_age=max_age, ) elif cors_prop and isinstance(cors_prop, bool) and cors_prop: cors = Cors( allow_origin="*", allow_methods=",".join(sorted(Route.ANY_HTTP_METHODS)), allow_headers=None, allow_credentials=None, max_age=None, ) return cors
def extract_cors(self, cors_prop: Union[Dict, str]) -> Optional[Cors]: """ Extract Cors property from AWS::Serverless::Api resource by reading and parsing Swagger documents. The result is added to the Api. Parameters ---------- cors_prop : dict Resource properties for Cors """ cors = None if cors_prop and isinstance(cors_prop, dict): allow_methods = self._get_cors_prop(cors_prop, "AllowMethods") if allow_methods: allow_methods = CfnBaseApiProvider.normalize_cors_allow_methods( allow_methods) else: allow_methods = ",".join(sorted(Route.ANY_HTTP_METHODS)) allow_origin = self._get_cors_prop(cors_prop, "AllowOrigin") allow_headers = self._get_cors_prop(cors_prop, "AllowHeaders") allow_credentials = self._get_cors_prop(cors_prop, "AllowCredentials", True) max_age = self._get_cors_prop(cors_prop, "MaxAge") cors = Cors( allow_origin=allow_origin, allow_methods=allow_methods, allow_headers=allow_headers, allow_credentials=allow_credentials, max_age=max_age, ) elif cors_prop and isinstance(cors_prop, str): allow_origin = cors_prop if not (allow_origin.startswith("'") and allow_origin.endswith("'")): raise InvalidSamDocumentException( "Cors Properties must be a quoted string " '(i.e. "\'*\'" is correct, but "*" is not).') allow_origin = allow_origin.strip("'") cors = Cors( allow_origin=allow_origin, allow_methods=",".join(sorted(Route.ANY_HTTP_METHODS)), allow_headers=None, allow_credentials=None, max_age=None, ) return cors
def test_basic_conversion(self): cors = Cors( allow_origin="*", allow_methods=",".join(["POST", "OPTIONS"]), allow_headers="UPGRADE-HEADER", max_age=6 ) headers = Cors.cors_to_headers(cors) self.assertEqual( headers, { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "POST,OPTIONS", "Access-Control-Allow-Headers": "UPGRADE-HEADER", "Access-Control-Max-Age": 6, }, )
def extract_cors_http(self, cors_prop): """ Extract Cors property from AWS::Serverless::HttpApi resource by reading and parsing Swagger documents. The result is added to the HttpApi. Parameters ---------- cors_prop : dict Resource properties for CorsConfiguration """ cors = None if cors_prop and isinstance(cors_prop, dict): allow_methods = self._get_cors_prop_http(cors_prop, "AllowMethods", list) if isinstance(allow_methods, list): allow_methods = self.normalize_cors_allow_methods( allow_methods) else: allow_methods = ",".join(sorted(Route.ANY_HTTP_METHODS)) allow_origins = self._get_cors_prop_http(cors_prop, "AllowOrigins", list) if isinstance(allow_origins, list): allow_origins = ",".join(allow_origins) allow_headers = self._get_cors_prop_http(cors_prop, "AllowHeaders", list) if isinstance(allow_headers, list): allow_headers = ",".join(allow_headers) max_age = self._get_cors_prop_http(cors_prop, "MaxAge", int) cors = Cors(allow_origin=allow_origins, allow_methods=allow_methods, allow_headers=allow_headers, max_age=max_age) elif cors_prop and isinstance(cors_prop, bool) and cors_prop: cors = Cors( allow_origin="*", allow_methods=",".join(sorted(Route.ANY_HTTP_METHODS)), allow_headers=None, max_age=None, ) return cors
def _request_handler(self, **kwargs): """ We handle all requests to the host:port. The general flow of handling a request is as follows * Fetch request from the Flask Global state. This is where Flask places the request and is per thread so multiple requests are still handled correctly * Find the Lambda function to invoke by doing a look up based on the request.endpoint and method * If we don't find the function, we will throw a 502 (just like the 404 and 405 responses we get from Flask. * Since we found a Lambda function to invoke, we construct the Lambda Event from the request * Then Invoke the Lambda function (docker container) * We then transform the response or errors we get from the Invoke and return the data back to the caller Parameters ---------- kwargs dict Keyword Args that are passed to the function from Flask. This happens when we have path parameters Returns ------- Response object """ route = self._get_current_route(request) cors_headers = Cors.cors_to_headers(self.api.cors) method, _ = self.get_request_methods_endpoints(request) if method == "OPTIONS" and self.api.cors: headers = Headers(cors_headers) return self.service_response("", headers, 200) try: event = self._construct_event(request, self.port, self.api.binary_media_types, self.api.stage_name, self.api.stage_variables) except UnicodeDecodeError: return ServiceErrorResponses.lambda_failure_response() stdout_stream = io.BytesIO() stdout_stream_writer = StreamWriter(stdout_stream, self.is_debugging) try: self.lambda_runner.invoke(route.function_name, event, stdout=stdout_stream_writer, stderr=self.stderr) except FunctionNotFound: return ServiceErrorResponses.lambda_not_found_response() lambda_response, lambda_logs, _ = LambdaOutputParser.get_lambda_output( stdout_stream) if self.stderr and lambda_logs: # Write the logs to stderr if available. self.stderr.write(lambda_logs) try: (status_code, headers, body) = self._parse_lambda_output(lambda_response, self.api.binary_media_types, request) except LambdaResponseParseException: LOG.error(str(LambdaResponseParseException)) return ServiceErrorResponses.lambda_failure_response() return self.service_response(body, headers, status_code)
def _request_handler(self, **kwargs): """ We handle all requests to the host:port. The general flow of handling a request is as follows * Fetch request from the Flask Global state. This is where Flask places the request and is per thread so multiple requests are still handled correctly * Find the Lambda function to invoke by doing a look up based on the request.endpoint and method * If we don't find the function, we will throw a 502 (just like the 404 and 405 responses we get from Flask. * Since we found a Lambda function to invoke, we construct the Lambda Event from the request * Then Invoke the Lambda function (docker container) * We then transform the response or errors we get from the Invoke and return the data back to the caller Parameters ---------- kwargs dict Keyword Args that are passed to the function from Flask. This happens when we have path parameters Returns ------- Response object """ route = self._get_current_route(request) cors_headers = Cors.cors_to_headers(self.api.cors) method, endpoint = self.get_request_methods_endpoints(request) if method == "OPTIONS" and self.api.cors: headers = Headers(cors_headers) return self.service_response("", headers, 200) try: # the Lambda Event 2.0 is only used for the HTTP API gateway with defined payload format version equal 2.0 # or none, as the default value to be used is 2.0 # https://docs.aws.amazon.com/apigatewayv2/latest/api-reference/apis-apiid-integrations.html#apis-apiid-integrations-prop-createintegrationinput-payloadformatversion if route.event_type == Route.HTTP and route.payload_format_version in [ None, "2.0" ]: route_key = self._v2_route_key(method, endpoint, route.is_default_route) event = self._construct_v_2_0_event_http( request, self.port, self.api.binary_media_types, self.api.stage_name, self.api.stage_variables, route_key, ) else: event = self._construct_v_1_0_event( request, self.port, self.api.binary_media_types, self.api.stage_name, self.api.stage_variables) except UnicodeDecodeError: return ServiceErrorResponses.lambda_failure_response() stdout_stream = io.BytesIO() stdout_stream_writer = StreamWriter(stdout_stream, self.is_debugging) try: self.lambda_runner.invoke(route.function_name, event, stdout=stdout_stream_writer, stderr=self.stderr) except FunctionNotFound: return ServiceErrorResponses.lambda_not_found_response() lambda_response, lambda_logs, _ = LambdaOutputParser.get_lambda_output( stdout_stream) if self.stderr and lambda_logs: # Write the logs to stderr if available. self.stderr.write(lambda_logs) try: if route.event_type == Route.HTTP and ( not route.payload_format_version or route.payload_format_version == "2.0"): (status_code, headers, body) = self._parse_v2_payload_format_lambda_output( lambda_response, self.api.binary_media_types, request) else: (status_code, headers, body) = self._parse_v1_payload_format_lambda_output( lambda_response, self.api.binary_media_types, request) except LambdaResponseParseException as ex: LOG.error("Invalid lambda response received: %s", ex) return ServiceErrorResponses.lambda_failure_response() return self.service_response(body, headers, status_code)