Skip to content

Commit bfc1041

Browse files
author
Michael Brewer
authored
refactor(data-classes): clean up internal logic
Clean up the internal logic for `APIGatewayAuthorizerResponse` and update the internal docs.
1 parent c8cf3ba commit bfc1041

File tree

2 files changed

+42
-36
lines changed

2 files changed

+42
-36
lines changed

aws_lambda_powertools/utilities/data_classes/api_gateway_authorizer_event.py

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -374,22 +374,22 @@ def __init__(
374374
Optional, context.
375375
Note: only names of type string and values of type int, string or boolean are supported
376376
"""
377-
self.principal_id = principal_id
378377
self.region = region
379378
self.aws_account_id = aws_account_id
380379
self.api_id = api_id
381380
self.stage = stage
381+
self.principal_id = principal_id
382382
self.context = context
383383
self._allow_routes: List[Dict] = []
384384
self._deny_routes: List[Dict] = []
385385

386-
def _add_route(self, effect: str, verb: str, resource: str, conditions: List[Dict]):
386+
def _add_route(self, effect: str, http_method: str, resource: str, conditions: Optional[List[Dict]] = None):
387387
"""Adds a route to the internal lists of allowed or denied routes. Each object in
388388
the internal list contains a resource ARN and a condition statement. The condition
389389
statement can be null."""
390-
if verb != "*" and verb not in HttpVerb.__members__:
390+
if http_method != "*" and http_method not in HttpVerb.__members__:
391391
allowed_values = [verb.value for verb in HttpVerb]
392-
raise ValueError(f"Invalid HTTP verb: '{verb}'. Use either '{allowed_values}'")
392+
raise ValueError(f"Invalid HTTP verb: '{http_method}'. Use either '{allowed_values}'")
393393

394394
resource_pattern = re.compile(self.path_regex)
395395
if not resource_pattern.match(resource):
@@ -398,7 +398,9 @@ def _add_route(self, effect: str, verb: str, resource: str, conditions: List[Dic
398398
if resource[:1] == "/":
399399
resource = resource[1:]
400400

401-
resource_arn = APIGatewayRouteArn(self.region, self.aws_account_id, self.api_id, self.stage, verb, resource).arn
401+
resource_arn = APIGatewayRouteArn(
402+
self.region, self.aws_account_id, self.api_id, self.stage, http_method, resource
403+
).arn
402404

403405
route = {"resourceArn": resource_arn, "conditions": conditions}
404406

@@ -412,24 +414,27 @@ def _get_empty_statement(effect: str) -> Dict[str, Any]:
412414
"""Returns an empty statement object prepopulated with the correct action and the desired effect."""
413415
return {"Action": "execute-api:Invoke", "Effect": effect.capitalize(), "Resource": []}
414416

415-
def _get_statement_for_effect(self, effect: str, methods: List) -> List:
416-
"""This function loops over an array of objects containing a resourceArn and
417-
conditions statement and generates the array of statements for the policy."""
418-
if len(methods) == 0:
417+
def _get_statement_for_effect(self, effect: str, routes: List[Dict]) -> List[Dict]:
418+
"""This function loops over an array of objects containing a `resourceArn` and
419+
`conditions` statement and generates the array of statements for the policy."""
420+
if len(routes) == 0:
419421
return []
420422

421-
statements = []
422-
423+
statements: List[Dict] = []
423424
statement = self._get_empty_statement(effect)
424-
for method in methods:
425-
if method["conditions"] is None or len(method["conditions"]) == 0:
426-
statement["Resource"].append(method["resourceArn"])
427-
else:
425+
426+
for route in routes:
427+
resource_arn = route["resourceArn"]
428+
conditions = route.get("conditions")
429+
if conditions is not None and len(conditions) > 0:
428430
conditional_statement = self._get_empty_statement(effect)
429-
conditional_statement["Resource"].append(method["resourceArn"])
430-
conditional_statement["Condition"] = method["conditions"]
431+
conditional_statement["Resource"].append(resource_arn)
432+
conditional_statement["Condition"] = conditions
431433
statements.append(conditional_statement)
432434

435+
else:
436+
statement["Resource"].append(resource_arn)
437+
433438
if len(statement["Resource"]) > 0:
434439
statements.append(statement)
435440

@@ -442,7 +447,7 @@ def allow_all_routes(self, http_method: str = HttpVerb.ALL.value):
442447
----------
443448
http_method: str
444449
"""
445-
self._add_route(effect="Allow", verb=http_method, resource="*", conditions=[])
450+
self._add_route(effect="Allow", http_method=http_method, resource="*")
446451

447452
def deny_all_routes(self, http_method: str = HttpVerb.ALL.value):
448453
"""Adds a '*' allow to the policy to deny access to all methods of an API
@@ -452,25 +457,23 @@ def deny_all_routes(self, http_method: str = HttpVerb.ALL.value):
452457
http_method: str
453458
"""
454459

455-
self._add_route(effect="Deny", verb=http_method, resource="*", conditions=[])
460+
self._add_route(effect="Deny", http_method=http_method, resource="*")
456461

457462
def allow_route(self, http_method: str, resource: str, conditions: Optional[List[Dict]] = None):
458463
"""Adds an API Gateway method (Http verb + Resource path) to the list of allowed
459464
methods for the policy.
460465
461466
Optionally includes a condition for the policy statement. More on AWS policy
462467
conditions here: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition"""
463-
conditions = conditions or []
464-
self._add_route(effect="Allow", verb=http_method, resource=resource, conditions=conditions)
468+
self._add_route(effect="Allow", http_method=http_method, resource=resource, conditions=conditions)
465469

466470
def deny_route(self, http_method: str, resource: str, conditions: Optional[List[Dict]] = None):
467471
"""Adds an API Gateway method (Http verb + Resource path) to the list of denied
468472
methods for the policy.
469473
470474
Optionally includes a condition for the policy statement. More on AWS policy
471475
conditions here: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition"""
472-
conditions = conditions or []
473-
self._add_route(effect="Deny", verb=http_method, resource=resource, conditions=conditions)
476+
self._add_route(effect="Deny", http_method=http_method, resource=resource, conditions=conditions)
474477

475478
def asdict(self) -> Dict[str, Any]:
476479
"""Generates the policy document based on the internal lists of allowed and denied

docs/utilities/data_classes.md

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ Use **`APIGatewayAuthorizerRequestEvent`** for type `REQUEST` and **`APIGatewayA
9696

9797
When the user is found, it includes the user details in the request context that will be available to the back-end, and returns a full access policy for admin users.
9898

99-
```python hl_lines="2-5 26-31 36-37 40 44 46"
99+
```python hl_lines="2-5 29 36-42 47 49"
100100
from aws_lambda_powertools.utilities.data_classes import event_source
101101
from aws_lambda_powertools.utilities.data_classes.api_gateway_authorizer_event import (
102102
APIGatewayAuthorizerRequestEvent,
@@ -106,11 +106,15 @@ Use **`APIGatewayAuthorizerRequestEvent`** for type `REQUEST` and **`APIGatewayA
106106
from secrets import compare_digest
107107

108108

109+
class UnAuthorizedError(Exception):
110+
...
111+
112+
109113
def get_user_by_token(token):
110114
if compare_digest(token, "admin-foo"):
111-
return {"isAdmin": True, "name": "Admin"}
115+
return {"id": 0, "name": "Admin", "isAdmin": True}
112116
elif compare_digest(token, "regular-foo"):
113-
return {"name": "Joe"}
117+
return {"id": 1, "name": "Joe"}
114118
else:
115119
return None
116120

@@ -119,25 +123,24 @@ Use **`APIGatewayAuthorizerRequestEvent`** for type `REQUEST` and **`APIGatewayA
119123
def handler(event: APIGatewayAuthorizerRequestEvent, context):
120124
user = get_user_by_token(event.get_header_value("Authorization"))
121125

126+
if user is None:
127+
# No user was found, so we raised a not authorized error
128+
raise UnAuthorizedError("Not authorized to perform this action")
129+
122130
# parse the `methodArn` as an `APIGatewayRouteArn`
123131
arn = event.parsed_arn
132+
124133
# Create the response builder from parts of the `methodArn`
134+
# and set the logged in user id and context
125135
policy = APIGatewayAuthorizerResponse(
126-
principal_id="user",
136+
principal_id=user["id"],
137+
context=user,
127138
region=arn.region,
128139
aws_account_id=arn.aws_account_id,
129140
api_id=arn.api_id,
130-
stage=arn.stage
141+
stage=arn.stage,
131142
)
132143

133-
if user is None:
134-
# No user was found, so we return not authorized
135-
policy.deny_all_routes()
136-
return policy.asdict()
137-
138-
# Found the user and setting the details in the context
139-
policy.context = user
140-
141144
# Conditional IAM Policy
142145
if user.get("isAdmin", False):
143146
policy.allow_all_routes()

0 commit comments

Comments
 (0)