Skip to content

Commit a5595d6

Browse files
wchengruTolledo
andauthored
Release/v1.28.0 (#1754) (#1756)
* Lambdaauth (#1733) * Add support for Lambda Authorizers in HttpAPI * Address comments and fix formatting * fix version * Validate input parameters. Update tests * black reformat Co-authored-by: Raymond Wang <[email protected]> * bump sam-translator version to v1.28.0 Co-authored-by: Tolledo <[email protected]> Co-authored-by: Tolledo <[email protected]>
1 parent 68596b6 commit a5595d6

18 files changed

+2145
-40
lines changed

samtranslator/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.27.0"
1+
__version__ = "1.28.0"

samtranslator/model/api/http_api_generator.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,11 @@ def _get_authorizers(self, authorizers_config, default_authorizer=None):
436436
authorization_scopes=authorizer.get("AuthorizationScopes"),
437437
jwt_configuration=authorizer.get("JwtConfiguration"),
438438
id_source=authorizer.get("IdentitySource"),
439+
function_arn=authorizer.get("FunctionArn"),
440+
function_invoke_role=authorizer.get("FunctionInvokeRole"),
441+
identity=authorizer.get("Identity"),
442+
authorizer_payload_format_version=authorizer.get("AuthorizerPayloadFormatVersion"),
443+
enable_simple_responses=authorizer.get("EnableSimpleResponses"),
439444
)
440445
return authorizers
441446

samtranslator/model/apigatewayv2.py

Lines changed: 192 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
from samtranslator.model import PropertyType, Resource
22
from samtranslator.model.types import is_type, one_of, is_str, list_of
3-
from samtranslator.model.intrinsics import ref
3+
from samtranslator.model.intrinsics import ref, fnSub
44
from samtranslator.model.exceptions import InvalidResourceException
5+
from samtranslator.translator.arn_generator import ArnGenerator
6+
7+
APIGATEWAY_AUTHORIZER_KEY = "x-amazon-apigateway-authorizer"
58

69

710
class ApiGatewayV2HttpApi(Resource):
@@ -57,39 +60,209 @@ class ApiGatewayV2ApiMapping(Resource):
5760

5861
class ApiGatewayV2Authorizer(object):
5962
def __init__(
60-
self, api_logical_id=None, name=None, authorization_scopes=[], jwt_configuration={}, id_source=None,
63+
self,
64+
api_logical_id=None,
65+
name=None,
66+
authorization_scopes=None,
67+
jwt_configuration=None,
68+
id_source=None,
69+
function_arn=None,
70+
function_invoke_role=None,
71+
identity=None,
72+
authorizer_payload_format_version=None,
73+
enable_simple_responses=None,
6174
):
6275
"""
6376
Creates an authorizer for use in V2 Http Apis
6477
"""
65-
if authorization_scopes is not None and not isinstance(authorization_scopes, list):
66-
raise InvalidResourceException(api_logical_id, "AuthorizationScopes must be a list.")
67-
68-
# Currently only one type of auth
69-
self.auth_type = "oauth2"
70-
7178
self.api_logical_id = api_logical_id
7279
self.name = name
7380
self.authorization_scopes = authorization_scopes
74-
75-
# Validate necessary parameters exist
76-
if not jwt_configuration:
77-
raise InvalidResourceException(api_logical_id, name + " Authorizer must define 'JwtConfiguration'.")
7881
self.jwt_configuration = jwt_configuration
79-
if not id_source:
80-
raise InvalidResourceException(api_logical_id, name + " Authorizer must define 'IdentitySource'.")
8182
self.id_source = id_source
83+
self.function_arn = function_arn
84+
self.function_invoke_role = function_invoke_role
85+
self.identity = identity
86+
self.authorizer_payload_format_version = authorizer_payload_format_version
87+
self.enable_simple_responses = enable_simple_responses
88+
89+
self._validate_input_parameters()
90+
91+
authorizer_type = self._get_auth_type()
92+
93+
# Validate necessary parameters exist
94+
if authorizer_type == "JWT":
95+
self._validate_jwt_authorizer()
96+
97+
if authorizer_type == "REQUEST":
98+
self._validate_lambda_authorizer()
99+
100+
def _get_auth_type(self):
101+
if self.jwt_configuration:
102+
return "JWT"
103+
return "REQUEST"
104+
105+
def _validate_input_parameters(self):
106+
authorizer_type = self._get_auth_type()
107+
108+
if self.authorization_scopes is not None and not isinstance(self.authorization_scopes, list):
109+
raise InvalidResourceException(self.api_logical_id, "AuthorizationScopes must be a list.")
110+
111+
if self.authorization_scopes is not None and not authorizer_type == "JWT":
112+
raise InvalidResourceException(
113+
self.api_logical_id, "AuthorizationScopes must be defined only for OAuth2 Authorizer."
114+
)
115+
116+
if self.jwt_configuration is not None and not authorizer_type == "JWT":
117+
raise InvalidResourceException(
118+
self.api_logical_id, "JwtConfiguration must be defined only for OAuth2 Authorizer."
119+
)
120+
121+
if self.id_source is not None and not authorizer_type == "JWT":
122+
raise InvalidResourceException(
123+
self.api_logical_id, "IdentitySource must be defined only for OAuth2 Authorizer."
124+
)
125+
126+
if self.function_arn is not None and not authorizer_type == "REQUEST":
127+
raise InvalidResourceException(
128+
self.api_logical_id, "FunctionArn must be defined only for Lambda Authorizer."
129+
)
130+
131+
if self.function_invoke_role is not None and not authorizer_type == "REQUEST":
132+
raise InvalidResourceException(
133+
self.api_logical_id, "FunctionInvokeRole must be defined only for Lambda Authorizer."
134+
)
135+
136+
if self.identity is not None and not authorizer_type == "REQUEST":
137+
raise InvalidResourceException(self.api_logical_id, "Identity must be defined only for Lambda Authorizer.")
138+
139+
if self.authorizer_payload_format_version is not None and not authorizer_type == "REQUEST":
140+
raise InvalidResourceException(
141+
self.api_logical_id, "AuthorizerPayloadFormatVersion must be defined only for Lambda Authorizer."
142+
)
143+
144+
if self.enable_simple_responses is not None and not authorizer_type == "REQUEST":
145+
raise InvalidResourceException(
146+
self.api_logical_id, "EnableSimpleResponses must be defined only for Lambda Authorizer."
147+
)
148+
149+
def _validate_jwt_authorizer(self):
150+
if not self.jwt_configuration:
151+
raise InvalidResourceException(
152+
self.api_logical_id, self.name + " OAuth2 Authorizer must define 'JwtConfiguration'."
153+
)
154+
if not self.id_source:
155+
raise InvalidResourceException(
156+
self.api_logical_id, self.name + " OAuth2 Authorizer must define 'IdentitySource'."
157+
)
158+
159+
def _validate_lambda_authorizer(self):
160+
if not self.function_arn:
161+
raise InvalidResourceException(
162+
self.api_logical_id, self.name + " Lambda Authorizer must define 'FunctionArn'."
163+
)
164+
if not self.authorizer_payload_format_version:
165+
raise InvalidResourceException(
166+
self.api_logical_id, self.name + " Lambda Authorizer must define 'AuthorizerPayloadFormatVersion'."
167+
)
82168

83169
def generate_openapi(self):
84170
"""
85171
Generates OAS for the securitySchemes section
86172
"""
87-
openapi = {
88-
"type": self.auth_type,
89-
"x-amazon-apigateway-authorizer": {
173+
authorizer_type = self._get_auth_type()
174+
175+
if authorizer_type == "JWT":
176+
openapi = {"type": "oauth2"}
177+
openapi[APIGATEWAY_AUTHORIZER_KEY] = {
90178
"jwtConfiguration": self.jwt_configuration,
91179
"identitySource": self.id_source,
92180
"type": "jwt",
93-
},
94-
}
181+
}
182+
183+
if authorizer_type == "REQUEST":
184+
openapi = {
185+
"type": "apiKey",
186+
"name": "Unused",
187+
"in": "header",
188+
}
189+
openapi[APIGATEWAY_AUTHORIZER_KEY] = {"type": "request"}
190+
191+
# Generate the lambda arn
192+
partition = ArnGenerator.get_partition_name()
193+
resource = "lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations"
194+
authorizer_uri = fnSub(
195+
ArnGenerator.generate_arn(
196+
partition=partition, service="apigateway", resource=resource, include_account_id=False
197+
),
198+
{"__FunctionArn__": self.function_arn},
199+
)
200+
openapi[APIGATEWAY_AUTHORIZER_KEY]["authorizerUri"] = authorizer_uri
201+
202+
# Set authorizerCredentials if present
203+
function_invoke_role = self._get_function_invoke_role()
204+
if function_invoke_role:
205+
openapi[APIGATEWAY_AUTHORIZER_KEY]["authorizerCredentials"] = function_invoke_role
206+
207+
# Set authorizerResultTtlInSeconds if present
208+
reauthorize_every = self._get_reauthorize_every()
209+
if reauthorize_every is not None:
210+
openapi[APIGATEWAY_AUTHORIZER_KEY]["authorizerResultTtlInSeconds"] = reauthorize_every
211+
212+
# Set identitySource if present
213+
if self.identity:
214+
openapi[APIGATEWAY_AUTHORIZER_KEY]["identitySource"] = self._get_identity_source()
215+
216+
# Set authorizerPayloadFormatVersion. It's a required parameter
217+
openapi[APIGATEWAY_AUTHORIZER_KEY][
218+
"authorizerPayloadFormatVersion"
219+
] = self.authorizer_payload_format_version
220+
221+
# Set authorizerPayloadFormatVersion. It's a required parameter
222+
if self.enable_simple_responses:
223+
openapi[APIGATEWAY_AUTHORIZER_KEY]["enableSimpleResponses"] = self.enable_simple_responses
224+
95225
return openapi
226+
227+
def _get_function_invoke_role(self):
228+
if not self.function_invoke_role or self.function_invoke_role == "NONE":
229+
return None
230+
231+
return self.function_invoke_role
232+
233+
def _get_identity_source(self):
234+
identity_source_headers = []
235+
identity_source_query_strings = []
236+
identity_source_stage_variables = []
237+
identity_source_context = []
238+
239+
if self.identity.get("Headers"):
240+
identity_source_headers = list(map(lambda h: "$request.header." + h, self.identity.get("Headers")))
241+
242+
if self.identity.get("QueryStrings"):
243+
identity_source_query_strings = list(
244+
map(lambda qs: "$request.querystring." + qs, self.identity.get("QueryStrings"))
245+
)
246+
247+
if self.identity.get("StageVariables"):
248+
identity_source_stage_variables = list(
249+
map(lambda sv: "$stageVariables." + sv, self.identity.get("StageVariables"))
250+
)
251+
252+
if self.identity.get("Context"):
253+
identity_source_context = list(map(lambda c: "$context." + c, self.identity.get("Context")))
254+
255+
identity_source = (
256+
identity_source_headers
257+
+ identity_source_query_strings
258+
+ identity_source_stage_variables
259+
+ identity_source_context
260+
)
261+
262+
return identity_source
263+
264+
def _get_reauthorize_every(self):
265+
if not self.identity:
266+
return None
267+
268+
return self.identity.get("ReauthorizeEvery")

samtranslator/model/stepfunctions/events.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def _generate_logical_id(self, prefix, suffix, resource_type):
4343
return logical_id
4444

4545
def _construct_role(self, resource, prefix=None, suffix=""):
46-
"""Constructs the IAM Role resource allowing the event service to invoke
46+
"""Constructs the IAM Role resource allowing the event service to invoke
4747
the StartExecution API of the state machine resource it is associated with.
4848
4949
:param model.stepfunctions.StepFunctionsStateMachine resource: The state machine resource associated with the event
@@ -135,7 +135,7 @@ class CloudWatchEvent(EventSource):
135135
}
136136

137137
def to_cloudformation(self, resource, **kwargs):
138-
"""Returns the CloudWatch Events/EventBridge Rule and IAM Role to which this
138+
"""Returns the CloudWatch Events/EventBridge Rule and IAM Role to which this
139139
CloudWatch Events/EventBridge event source corresponds.
140140
141141
:param dict kwargs: no existing resources need to be modified
@@ -379,10 +379,10 @@ def _add_swagger_integration(self, api, resource, role, intrinsics_resolver):
379379
api["DefinitionBody"] = editor.swagger
380380

381381
def _generate_request_template(self, resource):
382-
"""Generates the Body mapping request template for the Api. This allows for the input
382+
"""Generates the Body mapping request template for the Api. This allows for the input
383383
request to the Api to be passed as the execution input to the associated state machine resource.
384384
385-
:param model.stepfunctions.resources.StepFunctionsStateMachine resource; the state machine
385+
:param model.stepfunctions.resources.StepFunctionsStateMachine resource; the state machine
386386
resource to which the Api event source must be associated
387387
388388
:returns: a body mapping request which passes the Api input to the state machine execution

samtranslator/model/stepfunctions/generators.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def __init__(
5454
:param logical_id: Logical id of the SAM State Machine Resource
5555
:param depends_on: Any resources that need to be depended on
5656
:param managed_policy_map: Map of managed policy names to the ARNs
57-
:param intrinsics_resolver: Instance of the resolver that knows how to resolve parameter references
57+
:param intrinsics_resolver: Instance of the resolver that knows how to resolve parameter references
5858
:param definition: State Machine definition
5959
:param definition_uri: URI to State Machine definition
6060
:param logging: Logging configuration for the State Machine

samtranslator/open_api/open_api.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ def iter_on_path(self):
246246
def add_timeout_to_method(self, api, path, method_name, timeout):
247247
"""
248248
Adds a timeout to this path/method.
249-
249+
250250
:param dict api: Reference to the related Api's properties as defined in the template.
251251
:param string path: Path name
252252
:param string method_name: Method name
@@ -260,7 +260,7 @@ def add_timeout_to_method(self, api, path, method_name, timeout):
260260
def add_path_parameters_to_method(self, api, path, method_name, path_parameters):
261261
"""
262262
Adds path parameters to this path + method
263-
263+
264264
:param dict api: Reference to the related Api's properties as defined in the template.
265265
:param string path: Path name
266266
:param string method_name: Method name

0 commit comments

Comments
 (0)