Skip to content

Commit 78e5c27

Browse files
fix(event-sources): sane defaults for authorizer v1 and v2 (#4298)
* fix(parameters): make cache aware of single vs multiple calls Signed-off-by: heitorlessa <[email protected]> * chore: cleanup, add test for single and nested Signed-off-by: heitorlessa <[email protected]> * chore(ci): add first centralized reusable workflow * fix(event-sources): default dict and list in authorizers when not found * chore: mypy constant type * Delete bla.py * Delete playground/.prettierrc * Delete playground/app.mjs --------- Signed-off-by: heitorlessa <[email protected]> Co-authored-by: Leandro Damascena <[email protected]>
1 parent 8d05b17 commit 78e5c27

File tree

4 files changed

+58
-56
lines changed

4 files changed

+58
-56
lines changed

Diff for: aws_lambda_powertools/shared/constants.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161

6262
# JSON constants
6363
PRETTY_INDENT: int = 4
64-
COMPACT_INDENT = None
64+
COMPACT_INDENT: None = None
6565

6666
# Idempotency constants
6767
IDEMPOTENCY_DISABLED_ENV: str = "POWERTOOLS_IDEMPOTENCY_DISABLED"

Diff for: aws_lambda_powertools/utilities/data_classes/api_gateway_proxy_event.py

+44-43
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,18 @@
1515

1616
class APIGatewayEventAuthorizer(DictWrapper):
1717
@property
18-
def claims(self) -> Optional[Dict[str, Any]]:
19-
return self.get("claims")
18+
def claims(self) -> Dict[str, Any]:
19+
return self.get("claims") or {} # key might exist but can be `null`
2020

2121
@property
22-
def scopes(self) -> Optional[List[str]]:
23-
return self.get("scopes")
22+
def scopes(self) -> List[str]:
23+
return self.get("scopes") or [] # key might exist but can be `null`
2424

2525
@property
26-
def principal_id(self) -> Optional[str]:
26+
def principal_id(self) -> str:
2727
"""The principal user identification associated with the token sent by the client and returned from an
2828
API Gateway Lambda authorizer (formerly known as a custom authorizer)"""
29-
return self.get("principalId")
29+
return self.get("principalId") or "" # key might exist but can be `null`
3030

3131
@property
3232
def integration_latency(self) -> Optional[int]:
@@ -91,7 +91,8 @@ def route_key(self) -> Optional[str]:
9191

9292
@property
9393
def authorizer(self) -> APIGatewayEventAuthorizer:
94-
return APIGatewayEventAuthorizer(self._data["requestContext"]["authorizer"])
94+
authz_data = self._data.get("requestContext", {}).get("authorizer", {})
95+
return APIGatewayEventAuthorizer(authz_data)
9596

9697

9798
class APIGatewayProxyEvent(BaseProxyEvent):
@@ -112,11 +113,11 @@ def resource(self) -> str:
112113

113114
@property
114115
def multi_value_headers(self) -> Dict[str, List[str]]:
115-
return self.get("multiValueHeaders") or {}
116+
return self.get("multiValueHeaders") or {} # key might exist but can be `null`
116117

117118
@property
118119
def multi_value_query_string_parameters(self) -> Dict[str, List[str]]:
119-
return self.get("multiValueQueryStringParameters") or {}
120+
return self.get("multiValueQueryStringParameters") or {} # key might exist but can be `null`
120121

121122
@property
122123
def resolved_query_string_parameters(self) -> Dict[str, List[str]]:
@@ -154,72 +155,72 @@ def header_serializer(self) -> BaseHeadersSerializer:
154155

155156
class RequestContextV2AuthorizerIam(DictWrapper):
156157
@property
157-
def access_key(self) -> Optional[str]:
158+
def access_key(self) -> str:
158159
"""The IAM user access key associated with the request."""
159-
return self.get("accessKey")
160+
return self.get("accessKey") or "" # key might exist but can be `null`
160161

161162
@property
162-
def account_id(self) -> Optional[str]:
163+
def account_id(self) -> str:
163164
"""The AWS account ID associated with the request."""
164-
return self.get("accountId")
165+
return self.get("accountId") or "" # key might exist but can be `null`
165166

166167
@property
167-
def caller_id(self) -> Optional[str]:
168+
def caller_id(self) -> str:
168169
"""The principal identifier of the caller making the request."""
169-
return self.get("callerId")
170+
return self.get("callerId") or "" # key might exist but can be `null`
170171

171172
def _cognito_identity(self) -> Dict:
172-
return self.get("cognitoIdentity", {}) or {} # not available in FunctionURL
173+
return self.get("cognitoIdentity") or {} # not available in FunctionURL; key might exist but can be `null`
173174

174175
@property
175-
def cognito_amr(self) -> Optional[List[str]]:
176+
def cognito_amr(self) -> List[str]:
176177
"""This represents how the user was authenticated.
177178
AMR stands for Authentication Methods References as per the openid spec"""
178-
return self._cognito_identity().get("amr")
179+
return self._cognito_identity().get("amr", [])
179180

180181
@property
181-
def cognito_identity_id(self) -> Optional[str]:
182+
def cognito_identity_id(self) -> str:
182183
"""The Amazon Cognito identity ID of the caller making the request.
183184
Available only if the request was signed with Amazon Cognito credentials."""
184-
return self._cognito_identity().get("identityId")
185+
return self._cognito_identity().get("identityId", "")
185186

186187
@property
187-
def cognito_identity_pool_id(self) -> Optional[str]:
188+
def cognito_identity_pool_id(self) -> str:
188189
"""The Amazon Cognito identity pool ID of the caller making the request.
189190
Available only if the request was signed with Amazon Cognito credentials."""
190-
return self._cognito_identity().get("identityPoolId")
191+
return self._cognito_identity().get("identityPoolId") or "" # key might exist but can be `null`
191192

192193
@property
193-
def principal_org_id(self) -> Optional[str]:
194+
def principal_org_id(self) -> str:
194195
"""The AWS organization ID."""
195-
return self.get("principalOrgId")
196+
return self.get("principalOrgId") or "" # key might exist but can be `null`
196197

197198
@property
198-
def user_arn(self) -> Optional[str]:
199+
def user_arn(self) -> str:
199200
"""The Amazon Resource Name (ARN) of the effective user identified after authentication."""
200-
return self.get("userArn")
201+
return self.get("userArn") or "" # key might exist but can be `null`
201202

202203
@property
203-
def user_id(self) -> Optional[str]:
204+
def user_id(self) -> str:
204205
"""The IAM user ID of the effective user identified after authentication."""
205-
return self.get("userId")
206+
return self.get("userId") or "" # key might exist but can be `null`
206207

207208

208209
class RequestContextV2Authorizer(DictWrapper):
209210
@property
210-
def jwt_claim(self) -> Optional[Dict[str, Any]]:
211-
jwt = self.get("jwt") or {} # not available in FunctionURL
212-
return jwt.get("claims")
211+
def jwt_claim(self) -> Dict[str, Any]:
212+
jwt = self.get("jwt") or {} # not available in FunctionURL; key might exist but can be `null`
213+
return jwt.get("claims") or {} # key might exist but can be `null`
213214

214215
@property
215-
def jwt_scopes(self) -> Optional[List[str]]:
216-
jwt = self.get("jwt") or {} # not available in FunctionURL
217-
return jwt.get("scopes")
216+
def jwt_scopes(self) -> List[str]:
217+
jwt = self.get("jwt") or {} # not available in FunctionURL; key might exist but can be `null`
218+
return jwt.get("scopes", [])
218219

219220
@property
220-
def get_lambda(self) -> Optional[Dict[str, Any]]:
221+
def get_lambda(self) -> Dict[str, Any]:
221222
"""Lambda authorization context details"""
222-
return self.get("lambda")
223+
return self.get("lambda") or {} # key might exist but can be `null`
223224

224225
def get_context(self) -> Dict[str, Any]:
225226
"""Retrieve the authorization context details injected by a Lambda Authorizer.
@@ -238,20 +239,20 @@ def get_context(self) -> Dict[str, Any]:
238239
Dict[str, Any]
239240
A dictionary containing Lambda authorization context details.
240241
"""
241-
return self.get("lambda", {}) or {}
242+
return self.get_lambda
242243

243244
@property
244-
def iam(self) -> Optional[RequestContextV2AuthorizerIam]:
245+
def iam(self) -> RequestContextV2AuthorizerIam:
245246
"""IAM authorization details used for making the request."""
246-
iam = self.get("iam")
247-
return None if iam is None else RequestContextV2AuthorizerIam(iam)
247+
iam = self.get("iam") or {} # key might exist but can be `null`
248+
return RequestContextV2AuthorizerIam(iam)
248249

249250

250251
class RequestContextV2(BaseRequestContextV2):
251252
@property
252-
def authorizer(self) -> Optional[RequestContextV2Authorizer]:
253-
authorizer = self["requestContext"].get("authorizer")
254-
return None if authorizer is None else RequestContextV2Authorizer(authorizer)
253+
def authorizer(self) -> RequestContextV2Authorizer:
254+
ctx = self.get("requestContext") or {} # key might exist but can be `null`
255+
return RequestContextV2Authorizer(ctx.get("authorizer", {}))
255256

256257

257258
class APIGatewayProxyEventV2(BaseProxyEvent):

Diff for: tests/unit/data_classes/test_api_gateway_proxy_event.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,8 @@ def test_api_gateway_proxy_event():
8989
assert request_context.api_id == request_context_raw["apiId"]
9090

9191
authorizer = request_context.authorizer
92-
assert authorizer.claims is None
93-
assert authorizer.scopes is None
92+
assert authorizer.claims == {}
93+
assert authorizer.scopes == []
9494

9595
assert request_context.domain_name == request_context_raw["domainName"]
9696
assert request_context.domain_prefix == request_context_raw["domainPrefix"]
@@ -144,8 +144,8 @@ def test_api_gateway_proxy_event_with_principal_id():
144144

145145
request_context = parsed_event.request_context
146146
authorizer = request_context.authorizer
147-
assert authorizer.claims is None
148-
assert authorizer.scopes is None
147+
assert authorizer.claims == {}
148+
assert authorizer.scopes == []
149149
assert authorizer.principal_id == raw_event["requestContext"]["authorizer"]["principalId"]
150150
assert authorizer.integration_latency == raw_event["requestContext"]["authorizer"]["integrationLatency"]
151151
assert authorizer.get("integrationStatus", "failed") == "failed"

Diff for: tests/unit/data_classes/test_lambda_function_url.py

+9-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from aws_lambda_powertools.utilities.data_classes import LambdaFunctionUrlEvent
2+
from aws_lambda_powertools.utilities.data_classes.api_gateway_proxy_event import RequestContextV2Authorizer
23
from tests.functional.utils import load_event
34

45

@@ -47,7 +48,7 @@ def test_lambda_function_url_event():
4748
assert http.source_ip == http_raw["sourceIp"]
4849
assert http.user_agent == http_raw["userAgent"]
4950

50-
assert request_context.authorizer is None
51+
assert isinstance(request_context.authorizer, RequestContextV2Authorizer)
5152

5253

5354
def test_lambda_function_url_event_iam():
@@ -102,19 +103,19 @@ def test_lambda_function_url_event_iam():
102103

103104
authorizer = request_context.authorizer
104105
assert authorizer is not None
105-
assert authorizer.jwt_claim is None
106-
assert authorizer.jwt_scopes is None
107-
assert authorizer.get_lambda is None
106+
assert authorizer.jwt_claim == {}
107+
assert authorizer.jwt_scopes == []
108+
assert authorizer.get_lambda == {}
108109

109110
iam = authorizer.iam
110111
iam_raw = raw_event["requestContext"]["authorizer"]["iam"]
111112
assert iam is not None
112113
assert iam.access_key == iam_raw["accessKey"]
113114
assert iam.account_id == iam_raw["accountId"]
114115
assert iam.caller_id == iam_raw["callerId"]
115-
assert iam.cognito_amr is None
116-
assert iam.cognito_identity_id is None
117-
assert iam.cognito_identity_pool_id is None
118-
assert iam.principal_org_id is None
116+
assert iam.cognito_amr == []
117+
assert iam.cognito_identity_id == ""
118+
assert iam.cognito_identity_pool_id == ""
119+
assert iam.principal_org_id == ""
119120
assert iam.user_id == iam_raw["userId"]
120121
assert iam.user_arn == iam_raw["userArn"]

0 commit comments

Comments
 (0)