Skip to content

Commit d65f2a7

Browse files
authored
docs(data-classes): make authorizer concise; use enum (#630)
1 parent d02dd2e commit d65f2a7

File tree

3 files changed

+76
-65
lines changed

3 files changed

+76
-65
lines changed

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

+26-11
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import enum
12
import re
23
from typing import Any, Dict, List, Optional
34

@@ -312,7 +313,7 @@ def asdict(self) -> dict:
312313
return response
313314

314315

315-
class HttpVerb:
316+
class HttpVerb(enum.Enum):
316317
GET = "GET"
317318
POST = "POST"
318319
PUT = "PUT"
@@ -386,8 +387,9 @@ def _add_route(self, effect: str, verb: str, resource: str, conditions: List[Dic
386387
"""Adds a route to the internal lists of allowed or denied routes. Each object in
387388
the internal list contains a resource ARN and a condition statement. The condition
388389
statement can be null."""
389-
if verb != "*" and not hasattr(HttpVerb, verb):
390-
raise ValueError(f"Invalid HTTP verb {verb}. Allowed verbs in HttpVerb class")
390+
if verb != "*" and verb not in HttpVerb.__members__:
391+
allowed_values = [verb.value for verb in HttpVerb]
392+
raise ValueError(f"Invalid HTTP verb: '{verb}'. Use either '{allowed_values}'")
391393

392394
resource_pattern = re.compile(self.path_regex)
393395
if not resource_pattern.match(resource):
@@ -433,29 +435,42 @@ def _get_statement_for_effect(self, effect: str, methods: List) -> List:
433435

434436
return statements
435437

436-
def allow_all_routes(self):
437-
"""Adds a '*' allow to the policy to authorize access to all methods of an API"""
438-
self._add_route("Allow", HttpVerb.ALL, "*", [])
438+
def allow_all_routes(self, http_method: str = HttpVerb.ALL.value):
439+
"""Adds a '*' allow to the policy to authorize access to all methods of an API
439440
440-
def deny_all_routes(self):
441-
"""Adds a '*' allow to the policy to deny access to all methods of an API"""
442-
self._add_route("Deny", HttpVerb.ALL, "*", [])
441+
Parameters
442+
----------
443+
http_method: str
444+
"""
445+
self._add_route(effect="Allow", verb=http_method, resource="*", conditions=[])
446+
447+
def deny_all_routes(self, http_method: str = HttpVerb.ALL.value):
448+
"""Adds a '*' allow to the policy to deny access to all methods of an API
449+
450+
Parameters
451+
----------
452+
http_method: str
453+
"""
454+
455+
self._add_route(effect="Deny", verb=http_method, resource="*", conditions=[])
443456

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

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

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

Diff for: docs/utilities/data_classes.md

+38-30
Original file line numberDiff line numberDiff line change
@@ -87,19 +87,17 @@ Event Source | Data_class
8787

8888
> New in 1.20.0
8989
90-
It is used for API Gateway Rest API lambda authorizer payload. See docs on
91-
[Use API Gateway Lambda authorizers](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html){target="_blank"}
92-
for more details. Use `APIGatewayAuthorizerRequestEvent` for type "REQUEST" and `APIGatewayAuthorizerTokenEvent` for
93-
type "TOKEN".
90+
It is used for [API Gateway Rest API Lambda Authorizer payload](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html){target="_blank"}.
9491

95-
Below is 2 examples of a Rest API lambda authorizer. One looking up user details by `Authorization` header and using
96-
`APIGatewayAuthorizerResponse` to return the declined response when user is not found or authorized and include
97-
the user details in the request context and full access for admin users. And another using
98-
`APIGatewayAuthorizerTokenEvent` to get the `authorization_token`.
92+
Use **`APIGatewayAuthorizerRequestEvent`** for type `REQUEST` and **`APIGatewayAuthorizerTokenEvent`** for type `TOKEN`.
9993

10094
=== "app_type_request.py"
10195

102-
```python
96+
This example uses the `APIGatewayAuthorizerResponse` to decline a given request if the user is not found.
97+
98+
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.
99+
100+
```python hl_lines="2-5 26-31 36-37 40 44 46"
103101
from aws_lambda_powertools.utilities.data_classes import event_source
104102
from aws_lambda_powertools.utilities.data_classes.api_gateway_authorizer_event import (
105103
APIGatewayAuthorizerRequestEvent,
@@ -125,27 +123,33 @@ the user details in the request context and full access for admin users. And ano
125123
# parse the `methodArn` as an `APIGatewayRouteArn`
126124
arn = event.parsed_arn
127125
# Create the response builder from parts of the `methodArn`
128-
builder = APIGatewayAuthorizerResponse("user", arn.region, arn.aws_account_id, arn.api_id, arn.stage)
126+
policy = APIGatewayAuthorizerResponse(
127+
principal_id="user",
128+
region=arn.region,
129+
aws_account_id=arn.aws_account_id,
130+
api_id=arn.api_id,
131+
stage=arn.stage
132+
)
129133

130134
if user is None:
131135
# No user was found, so we return not authorized
132-
builder.deny_all_routes()
133-
return builder.asdict()
136+
policy.deny_all_routes()
137+
return policy.asdict()
134138

135139
# Found the user and setting the details in the context
136-
builder.context = user
140+
policy.context = user
137141

138142
# Conditional IAM Policy
139143
if user.get("isAdmin", False):
140-
builder.allow_all_routes()
144+
policy.allow_all_routes()
141145
else:
142-
builder.allow_route(HttpVerb.GET, "/user-profile")
146+
policy.allow_route(HttpVerb.GET, "/user-profile")
143147

144-
return builder.asdict()
148+
return policy.asdict()
145149
```
146150
=== "app_type_token.py"
147151

148-
```python
152+
```python hl_lines="2-5 12-18 21 23-24"
149153
from aws_lambda_powertools.utilities.data_classes import event_source
150154
from aws_lambda_powertools.utilities.data_classes.api_gateway_authorizer_event import (
151155
APIGatewayAuthorizerTokenEvent,
@@ -156,30 +160,34 @@ the user details in the request context and full access for admin users. And ano
156160
@event_source(data_class=APIGatewayAuthorizerTokenEvent)
157161
def handler(event: APIGatewayAuthorizerTokenEvent, context):
158162
arn = event.parsed_arn
159-
builder = APIGatewayAuthorizerResponse("user", arn.region, arn.aws_account_id, arn.api_id, arn.stage)
163+
164+
policy = APIGatewayAuthorizerResponse(
165+
principal_id="user",
166+
region=arn.region,
167+
aws_account_id=arn.aws_account_id,
168+
api_id=arn.api_id,
169+
stage=arn.stage
170+
)
171+
160172
if event.authorization_token == "42":
161-
builder.allow_all_methods()
173+
policy.allow_all_routes()
162174
else:
163-
builder.deny_all_methods()
164-
return builder.asdict()
175+
policy.deny_all_routes()
176+
return policy.asdict()
165177
```
166178

167179
### API Gateway Authorizer V2
168180

169181
> New in 1.20.0
170182
171-
It is used for API Gateway HTTP API lambda authorizer payload version 2. See blog post
172-
[Introducing IAM and Lambda authorizers for Amazon API Gateway HTTP APIs](https://aws.amazon.com/blogs/compute/introducing-iam-and-lambda-authorizers-for-amazon-api-gateway-http-apis/){target="_blank"}
173-
or [Working with AWS Lambda authorizers for HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html){target="_blank"}
174-
for more details
175-
176-
Below is a simple example of an HTTP API lambda authorizer looking up user details by `x-token` header and using
177-
`APIGatewayAuthorizerResponseV2` to return the declined response when user is not found or authorized and include
178-
the user details in the request context.
183+
It is used for [API Gateway HTTP API Lambda Authorizer payload version 2](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html){target="_blank"}.
184+
See also [this blog post](https://aws.amazon.com/blogs/compute/introducing-iam-and-lambda-authorizers-for-amazon-api-gateway-http-apis/){target="_blank"} for more details.
179185

180186
=== "app.py"
181187

182-
```python
188+
This example looks up user details via `x-token` header. It uses `APIGatewayAuthorizerResponseV2` to return a deny policy when user is not found or authorized.
189+
190+
```python hl_lines="2-5 21 24"
183191
from aws_lambda_powertools.utilities.data_classes import event_source
184192
from aws_lambda_powertools.utilities.data_classes.api_gateway_authorizer_event import (
185193
APIGatewayAuthorizerEventV2,

Diff for: tests/functional/data_classes/test_api_gateway_authorizer.py

+12-24
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,17 @@ def test_authorizer_response_no_statement(builder: APIGatewayAuthorizerResponse)
2222

2323

2424
def test_authorizer_response_invalid_verb(builder: APIGatewayAuthorizerResponse):
25-
with pytest.raises(ValueError) as ex:
25+
with pytest.raises(ValueError, match="Invalid HTTP verb: 'INVALID'"):
2626
# GIVEN a invalid http_method
2727
# WHEN calling deny_method
28-
builder.deny_route("INVALID", "foo")
29-
30-
# THEN raise a name error for invalid http verb
31-
assert str(ex.value) == "Invalid HTTP verb INVALID. Allowed verbs in HttpVerb class"
28+
builder.deny_route(http_method="INVALID", resource="foo")
3229

3330

3431
def test_authorizer_response_invalid_resource(builder: APIGatewayAuthorizerResponse):
35-
with pytest.raises(ValueError) as ex:
32+
with pytest.raises(ValueError, match="Invalid resource path: \$."): # noqa: W605
3633
# GIVEN a invalid resource path "$"
3734
# WHEN calling deny_method
38-
builder.deny_route(HttpVerb.GET, "$")
39-
40-
# THEN raise a name error for invalid resource
41-
assert "Invalid resource path: $" in str(ex.value)
35+
builder.deny_route(http_method=HttpVerb.GET.value, resource="$")
4236

4337

4438
def test_authorizer_response_allow_all_routes_with_context():
@@ -78,7 +72,7 @@ def test_authorizer_response_deny_all_routes(builder: APIGatewayAuthorizerRespon
7872

7973

8074
def test_authorizer_response_allow_route(builder: APIGatewayAuthorizerResponse):
81-
builder.allow_route(HttpVerb.GET, "/foo")
75+
builder.allow_route(http_method=HttpVerb.GET.value, resource="/foo")
8276
assert builder.asdict() == {
8377
"policyDocument": {
8478
"Version": "2012-10-17",
@@ -95,7 +89,7 @@ def test_authorizer_response_allow_route(builder: APIGatewayAuthorizerResponse):
9589

9690

9791
def test_authorizer_response_deny_route(builder: APIGatewayAuthorizerResponse):
98-
builder.deny_route(HttpVerb.PUT, "foo")
92+
builder.deny_route(http_method=HttpVerb.PUT.value, resource="foo")
9993
assert builder.asdict() == {
10094
"principalId": "foo",
10195
"policyDocument": {
@@ -112,12 +106,11 @@ def test_authorizer_response_deny_route(builder: APIGatewayAuthorizerResponse):
112106

113107

114108
def test_authorizer_response_allow_route_with_conditions(builder: APIGatewayAuthorizerResponse):
109+
condition = {"StringEquals": {"method.request.header.Content-Type": "text/html"}}
115110
builder.allow_route(
116-
HttpVerb.POST,
117-
"/foo",
118-
[
119-
{"StringEquals": {"method.request.header.Content-Type": "text/html"}},
120-
],
111+
http_method=HttpVerb.POST.value,
112+
resource="/foo",
113+
conditions=[condition],
121114
)
122115
assert builder.asdict() == {
123116
"principalId": "foo",
@@ -136,13 +129,8 @@ def test_authorizer_response_allow_route_with_conditions(builder: APIGatewayAuth
136129

137130

138131
def test_authorizer_response_deny_route_with_conditions(builder: APIGatewayAuthorizerResponse):
139-
builder.deny_route(
140-
HttpVerb.POST,
141-
"/foo",
142-
[
143-
{"StringEquals": {"method.request.header.Content-Type": "application/json"}},
144-
],
145-
)
132+
condition = {"StringEquals": {"method.request.header.Content-Type": "application/json"}}
133+
builder.deny_route(http_method=HttpVerb.POST.value, resource="/foo", conditions=[condition])
146134
assert builder.asdict() == {
147135
"principalId": "foo",
148136
"policyDocument": {

0 commit comments

Comments
 (0)