fastapi-aws 0.0.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastapi_aws/__init__.py +7 -0
- fastapi_aws/__main__.py +342 -0
- fastapi_aws/_version.py +21 -0
- fastapi_aws/authorizers.py +182 -0
- fastapi_aws/integrations.py +306 -0
- fastapi_aws/models.py +57 -0
- fastapi_aws/route.py +191 -0
- fastapi_aws/router.py +45 -0
- fastapi_aws-0.0.7.dist-info/METADATA +332 -0
- fastapi_aws-0.0.7.dist-info/RECORD +12 -0
- fastapi_aws-0.0.7.dist-info/WHEEL +5 -0
- fastapi_aws-0.0.7.dist-info/top_level.txt +1 -0
fastapi_aws/__init__.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from ._version import version as __version__
|
|
2
|
+
|
|
3
|
+
from .route import AWSAPIRoute
|
|
4
|
+
from .router import AWSAPIRouter
|
|
5
|
+
from .authorizers import CognitoAuthorizer, APIKeyAuthorizer, LambdaAuthorizer
|
|
6
|
+
|
|
7
|
+
__all__ = ["AWSAPIRoute", "AWSAPIRouter", "CognitoAuthorizer", "APIKeyAuthorizer", "LambdaAuthorizer"]
|
fastapi_aws/__main__.py
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"""export the openapi.json to a given directory
|
|
2
|
+
|
|
3
|
+
This script loads a fastapi.router from the --router parameter and creates two openapi specs:
|
|
4
|
+
+ public for sharing with public consumers of the api.
|
|
5
|
+
+ private with CORS and integration definitions for the aws apigateway to consume.
|
|
6
|
+
"""
|
|
7
|
+
import sys
|
|
8
|
+
import os
|
|
9
|
+
import argparse
|
|
10
|
+
import json
|
|
11
|
+
import fnmatch
|
|
12
|
+
|
|
13
|
+
from uvicorn.importer import import_from_string
|
|
14
|
+
|
|
15
|
+
from fastapi import FastAPI, Request, Response, APIRouter, Depends, Header
|
|
16
|
+
from fastapi.routing import APIRoute
|
|
17
|
+
from fastapi.responses import JSONResponse
|
|
18
|
+
from fastapi.openapi.utils import get_openapi
|
|
19
|
+
|
|
20
|
+
# from fastapi.middleware.cors import CORSMiddleware
|
|
21
|
+
|
|
22
|
+
OPENAPI_VERSION = os.getenv("OPENAPI_VERSION", "3.0.1")
|
|
23
|
+
CORS_ORIGINS = os.getenv("CORS_ORIGINS", "*").split(",")
|
|
24
|
+
CORS_HEADERS = os.getenv("CORS_HEADERS", "Content-Type,Authorization").split(",")
|
|
25
|
+
CORS_METHODS = os.getenv("CORS_METHODS", "*").split(",")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def cors_origins() -> str:
|
|
29
|
+
"""format the cors origins for apigw
|
|
30
|
+
NB: we only really allow the wildcard or a single origin, so not sure what use this is
|
|
31
|
+
"""
|
|
32
|
+
return "'%s'" % ",".join(CORS_ORIGINS)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def cors_headers() -> str:
|
|
36
|
+
"""format the cors headers for apigw"""
|
|
37
|
+
return "'%s'" % ",".join(CORS_HEADERS)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def cors_methods() -> str:
|
|
41
|
+
"""format the cors methods for apigw"""
|
|
42
|
+
# return "'%s'" % ",".join(CORS_METHODS)
|
|
43
|
+
return "'OPTIONS,GET,POST,DELETE'"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def add_cors_headers(response: Response):
|
|
47
|
+
"""add CORS headers to response objects for fastapi"""
|
|
48
|
+
response.headers["Access-Control-Allow-Origin"] = cors_origins()
|
|
49
|
+
response.headers["Access-Control-Allow-Headers"] = cors_headers()
|
|
50
|
+
response.headers["Access-Control-Allow-Methods"] = cors_methods()
|
|
51
|
+
return response
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def cors_headers_dependency(
|
|
55
|
+
response: Response,
|
|
56
|
+
access_control_allow_origin: str = Header(),
|
|
57
|
+
access_control_allow_headers: str = Header(),
|
|
58
|
+
access_control_allow_methods: str = Header(),
|
|
59
|
+
):
|
|
60
|
+
return add_cors_headers(response)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def add_cors_dependency_to_router(router):
|
|
64
|
+
"""Add CORS headers dependency to all routes in the router"""
|
|
65
|
+
for route in router.routes:
|
|
66
|
+
if any(x in route.methods for x in CORS_METHODS):
|
|
67
|
+
route.dependencies.append(Depends(cors_headers_dependency))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def cors_headers_schema():
|
|
71
|
+
return {
|
|
72
|
+
"Access-Control-Allow-Origin": {"schema": {"type": "string"}},
|
|
73
|
+
"Access-Control-Allow-Methods": {"schema": {"type": "string"}},
|
|
74
|
+
"Access-Control-Allow-Headers": {"schema": {"type": "string"}},
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def cors_response_defaults():
|
|
79
|
+
return {
|
|
80
|
+
"method.response.header.Access-Control-Allow-Methods": cors_methods(),
|
|
81
|
+
"method.response.header.Access-Control-Allow-Headers": cors_headers(),
|
|
82
|
+
"method.response.header.Access-Control-Allow-Origin": cors_origins(),
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def add_cors_preflight_routes(app: FastAPI):
|
|
87
|
+
"""automatically add OPTIONS routes for CORS
|
|
88
|
+
|
|
89
|
+
These are require for the apigw spec, otherwise CORS requests fails.
|
|
90
|
+
|
|
91
|
+
NB: these preflight routes are added via fastapi, but we could do directly via schema modification
|
|
92
|
+
"""
|
|
93
|
+
opt_router = APIRouter(dependencies=[Depends(cors_headers)])
|
|
94
|
+
rts = [r for r in app.routes if isinstance(r, APIRoute)]
|
|
95
|
+
for route in rts:
|
|
96
|
+
print("route: '%s'" % str(route))
|
|
97
|
+
|
|
98
|
+
async def options_handler(request: Request):
|
|
99
|
+
return add_cors_headers(JSONResponse(content={}))
|
|
100
|
+
|
|
101
|
+
opt_router.add_api_route(
|
|
102
|
+
path=route.path,
|
|
103
|
+
endpoint=options_handler,
|
|
104
|
+
methods=["OPTIONS"],
|
|
105
|
+
# tags=route.tags if route.tags else None,
|
|
106
|
+
# summary=f"Options for {route.summary}" if route.summary else None,
|
|
107
|
+
# include_in_schema=False,
|
|
108
|
+
tags=(route.tags or []) + ["CORS"],
|
|
109
|
+
responses={
|
|
110
|
+
"200": {"description": "200 response", "headers": cors_headers_schema()}
|
|
111
|
+
},
|
|
112
|
+
openapi_extra={
|
|
113
|
+
"x-amazon-apigateway-integration": {
|
|
114
|
+
"responses": {
|
|
115
|
+
"default": {
|
|
116
|
+
"statusCode": "200",
|
|
117
|
+
"responseParameters": cors_response_defaults(),
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
"passthroughBehavior": "when_no_match",
|
|
121
|
+
"timeoutInMillis": 29000,
|
|
122
|
+
"requestTemplates": {
|
|
123
|
+
"application/json": json.dumps({"statusCode": 200})
|
|
124
|
+
},
|
|
125
|
+
"type": "mock",
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
return opt_router
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def inject_cors_headers(openapi_schema):
|
|
134
|
+
"""Inject CORS headers into all responses in the OpenAPI schema.
|
|
135
|
+
|
|
136
|
+
This ensures that all responses (200, 4xx, 5xx, etc.) include:
|
|
137
|
+
- Access-Control-Allow-Origin
|
|
138
|
+
- Access-Control-Allow-Headers
|
|
139
|
+
- Access-Control-Allow-Methods
|
|
140
|
+
|
|
141
|
+
Required for AWS API Gateway to properly handle CORS for REST APIs.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
openapi_schema (dict): The OpenAPI JSON schema.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
dict: Updated OpenAPI schema with CORS headers injected.
|
|
148
|
+
"""
|
|
149
|
+
cors_headers = cors_headers_schema()
|
|
150
|
+
cors_response_parameters = cors_response_defaults()
|
|
151
|
+
|
|
152
|
+
for path, methods in openapi_schema.get("paths", {}).items():
|
|
153
|
+
for method, details in methods.items():
|
|
154
|
+
# Skip OPTIONS, as it's already handled by add_cors_preflight_routes()
|
|
155
|
+
if method.upper() == "OPTIONS":
|
|
156
|
+
continue
|
|
157
|
+
|
|
158
|
+
# Ensure responses exist
|
|
159
|
+
if "responses" not in details:
|
|
160
|
+
details["responses"] = {}
|
|
161
|
+
|
|
162
|
+
# Iterate through each response (e.g., 200, 400, 500)
|
|
163
|
+
for status_code, response in details["responses"].items():
|
|
164
|
+
if "headers" not in response:
|
|
165
|
+
response["headers"] = {}
|
|
166
|
+
|
|
167
|
+
# Inject CORS headers into each response
|
|
168
|
+
response["headers"].update(cors_headers)
|
|
169
|
+
|
|
170
|
+
# Ensure x-amazon-apigateway-integration exists
|
|
171
|
+
if "x-amazon-apigateway-integration" in details:
|
|
172
|
+
integration = details["x-amazon-apigateway-integration"]
|
|
173
|
+
|
|
174
|
+
if "responses" in integration:
|
|
175
|
+
for response_key, integration_response in integration[
|
|
176
|
+
"responses"
|
|
177
|
+
].items():
|
|
178
|
+
if "responseParameters" not in integration_response:
|
|
179
|
+
integration_response["responseParameters"] = {}
|
|
180
|
+
|
|
181
|
+
# Inject responseParameters into API Gateway integration responses
|
|
182
|
+
integration_response["responseParameters"].update(
|
|
183
|
+
cors_response_parameters
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
return openapi_schema
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def remove_keys_by_pattern(obj, pattern):
|
|
190
|
+
"""Recursively remove keys from a dict matching the given pattern."""
|
|
191
|
+
if isinstance(obj, dict):
|
|
192
|
+
keys_to_delete = [key for key in obj if fnmatch.fnmatch(key, pattern)]
|
|
193
|
+
for key in keys_to_delete:
|
|
194
|
+
del obj[key]
|
|
195
|
+
for value in obj.values():
|
|
196
|
+
remove_keys_by_pattern(value, pattern)
|
|
197
|
+
elif isinstance(obj, list):
|
|
198
|
+
for item in obj:
|
|
199
|
+
remove_keys_by_pattern(item, pattern)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def make_public_api_schema(openapi_schema):
|
|
203
|
+
"""Create a public version of the API schema with private data scrubbed.
|
|
204
|
+
|
|
205
|
+
This will match for any "x-amazon-apigateway-*" pattern.
|
|
206
|
+
Including:
|
|
207
|
+
+ x-amazon-apigateway-integration
|
|
208
|
+
+ x-amazon-apigateway-authtype
|
|
209
|
+
+ x-amazon-apigateway-authorizer
|
|
210
|
+
"""
|
|
211
|
+
remove_keys_by_pattern(openapi_schema, "x-amazon-apigateway-*")
|
|
212
|
+
return openapi_schema
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def default_cors_headers(define_cors=True):
|
|
216
|
+
headers = {
|
|
217
|
+
"allowOrigins": "'*'",
|
|
218
|
+
"allowMethods": ["'%s'" % x for x in CORS_METHODS],
|
|
219
|
+
"allowHeaders": ["'%s'" % x for x in CORS_HEADERS],
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return {"x-amazon-apigateway-cors": headers}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def aws_gateway_responses(define_cors=True):
|
|
226
|
+
"""Add custom apigw responses to the openapi schema
|
|
227
|
+
see: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-gateway-responses.html
|
|
228
|
+
"""
|
|
229
|
+
default_cors = {
|
|
230
|
+
"gatewayresponse.header.Access-Control-Allow-Origin": cors_origins(),
|
|
231
|
+
"gatewayresponse.header.Access-Control-Allow-Methods": cors_methods(),
|
|
232
|
+
"gatewayresponse.header.Access-Control-Allow-Headers": cors_headers(),
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
responses = {
|
|
236
|
+
"DEFAULT_4XX": {
|
|
237
|
+
"statusCode": 400,
|
|
238
|
+
"responseTemplates": {
|
|
239
|
+
"application/json": json.dumps({"message": "Client error occurred"})
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
"DEFAULT_5XX": {
|
|
243
|
+
"statusCode": 500,
|
|
244
|
+
"responseTemplates": {
|
|
245
|
+
"application/json": json.dumps({"message": "Internal server error"})
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
"ACCESS_DENIED": {
|
|
249
|
+
"statusCode": 403,
|
|
250
|
+
"responseTemplates": {
|
|
251
|
+
"application/json": json.dumps({"message": "Access Denied"})
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
"UNAUTHORIZED": {
|
|
255
|
+
"statusCode": 401,
|
|
256
|
+
"responseTemplates": {
|
|
257
|
+
"application/json": json.dumps({"message": "Unauthorized"})
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
"MISSING_AUTHENTICATION_TOKEN": {
|
|
261
|
+
"statusCode": 404,
|
|
262
|
+
"responseTemplates": {
|
|
263
|
+
"application/json": json.dumps({"message": "Route not found"})
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if define_cors:
|
|
269
|
+
for k in responses:
|
|
270
|
+
responses[k].update({"responseParameters": default_cors})
|
|
271
|
+
|
|
272
|
+
return {"x-amazon-apigateway-gateway-responses": responses}
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
if __name__ == "__main__":
|
|
276
|
+
parser = argparse.ArgumentParser()
|
|
277
|
+
parser.add_argument(
|
|
278
|
+
"app", help="app import string. e.g. 'main:app'", default="main:app"
|
|
279
|
+
)
|
|
280
|
+
parser.add_argument("--router", help="router import string", default=None)
|
|
281
|
+
parser.add_argument("-t", "--title", help="title of the API", default="untitled")
|
|
282
|
+
parser.add_argument("-v", "--version", help="version of the API", default="0.0.1")
|
|
283
|
+
parser.add_argument(
|
|
284
|
+
"--out-public",
|
|
285
|
+
help="public openapi definition (x-integration information removed)",
|
|
286
|
+
default="-",
|
|
287
|
+
)
|
|
288
|
+
parser.add_argument(
|
|
289
|
+
"--out-private",
|
|
290
|
+
help="openapi filename with x-integration information",
|
|
291
|
+
default=None,
|
|
292
|
+
)
|
|
293
|
+
parser.add_argument(
|
|
294
|
+
"--cors",
|
|
295
|
+
default=True,
|
|
296
|
+
action="store_true",
|
|
297
|
+
help="include CORS methods and resources for pre-flight responses",
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
args = parser.parse_args()
|
|
301
|
+
|
|
302
|
+
print(f"importing app from {args.app}")
|
|
303
|
+
router = import_from_string(args.router)
|
|
304
|
+
print(f"imported router: '{type(router)}'")
|
|
305
|
+
|
|
306
|
+
if router is None:
|
|
307
|
+
print("ERR: must include a router")
|
|
308
|
+
sys.exit(1)
|
|
309
|
+
|
|
310
|
+
app = FastAPI(default_route_class=type(router))
|
|
311
|
+
app.router = router
|
|
312
|
+
|
|
313
|
+
# print(app.routes)
|
|
314
|
+
|
|
315
|
+
if args.cors:
|
|
316
|
+
app.include_router(add_cors_preflight_routes(app))
|
|
317
|
+
|
|
318
|
+
# print(app.routes)
|
|
319
|
+
|
|
320
|
+
openapi_schema = get_openapi(
|
|
321
|
+
title=args.title,
|
|
322
|
+
version=args.version,
|
|
323
|
+
openapi_version=OPENAPI_VERSION,
|
|
324
|
+
routes=app.routes,
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
# if args.cors:
|
|
328
|
+
# add_cors_responses(openapi_schema)
|
|
329
|
+
|
|
330
|
+
openapi_schema.update(aws_gateway_responses(args.cors))
|
|
331
|
+
openapi_schema.update(default_cors_headers(args.cors))
|
|
332
|
+
openapi_schema = inject_cors_headers(openapi_schema)
|
|
333
|
+
|
|
334
|
+
# write the private api definition (wuth all x-amazon-apigateway-integration info)
|
|
335
|
+
private = openapi_schema
|
|
336
|
+
with open(args.out_private, "w") as f:
|
|
337
|
+
json.dump(private, f, indent=2)
|
|
338
|
+
|
|
339
|
+
# write the public api definition (with all x-amazon-apigateway-integration and cors data scrubbed)
|
|
340
|
+
public = make_public_api_schema(openapi_schema)
|
|
341
|
+
with open(args.out_public, "w") as f:
|
|
342
|
+
json.dump(public, f, indent=2)
|
fastapi_aws/_version.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
6
|
+
TYPE_CHECKING = False
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
11
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
12
|
+
else:
|
|
13
|
+
VERSION_TUPLE = object
|
|
14
|
+
|
|
15
|
+
version: str
|
|
16
|
+
__version__: str
|
|
17
|
+
__version_tuple__: VERSION_TUPLE
|
|
18
|
+
version_tuple: VERSION_TUPLE
|
|
19
|
+
|
|
20
|
+
__version__ = version = '0.0.7'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 0, 7)
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""AWS APIGateway Authorizers defined for openapi spec
|
|
2
|
+
|
|
3
|
+
Apply these security schemas on routers and endpoints to export an openapi
|
|
4
|
+
spec with aws integrations.
|
|
5
|
+
|
|
6
|
+
TODO: aws can have lambda authorizers as request or token types; however, in
|
|
7
|
+
this implementation the APIKeyAuthorizer accepts token-type auth but
|
|
8
|
+
does not allow lambda definitions, and the lambda definition allows
|
|
9
|
+
lambda uri but only request-type auth. This is a limitation.
|
|
10
|
+
|
|
11
|
+
refs:
|
|
12
|
+
+ https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-api-key-source.html
|
|
13
|
+
+ https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-auth.html
|
|
14
|
+
+ https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-authorizer.html
|
|
15
|
+
"""
|
|
16
|
+
from fastapi import Request
|
|
17
|
+
from fastapi.security import HTTPBearer
|
|
18
|
+
from fastapi.openapi.models import APIKey
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AWSAuthorizer(HTTPBearer):
|
|
22
|
+
"""Base class for all AWS authorizers
|
|
23
|
+
|
|
24
|
+
type: str, should be one of ("token", "request", "cognito_user_pools")
|
|
25
|
+
|
|
26
|
+
ref:
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
DEFAULT_HEADER_FIELDNAME = "Authorization"
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
authorizer_name: str,
|
|
34
|
+
authorizer_type: str,
|
|
35
|
+
auto_error: bool = True,
|
|
36
|
+
header_names: list = None,
|
|
37
|
+
ttl: int = 0,
|
|
38
|
+
):
|
|
39
|
+
self.scheme_name = authorizer_name
|
|
40
|
+
self.auto_error = auto_error
|
|
41
|
+
self.ttl = ttl
|
|
42
|
+
|
|
43
|
+
assert authorizer_type in ("token", "request", "cognito_user_pools")
|
|
44
|
+
self.authorizer_type = authorizer_type
|
|
45
|
+
|
|
46
|
+
if header_names is None:
|
|
47
|
+
self.header_names = [AWSAuthorizer.DEFAULT_HEADER_FIELDNAME]
|
|
48
|
+
elif not isinstance(header_names, list):
|
|
49
|
+
self.header_names = [header_names]
|
|
50
|
+
else:
|
|
51
|
+
self.header_names = header_names
|
|
52
|
+
|
|
53
|
+
self.model = self._create_model()
|
|
54
|
+
|
|
55
|
+
def _create_model(self):
|
|
56
|
+
raise NotImplementedError()
|
|
57
|
+
|
|
58
|
+
async def __call__(self, request: Request):
|
|
59
|
+
"""this class does not do any actual auth"""
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class CognitoAuthorizer(AWSAuthorizer):
|
|
64
|
+
"""Fake cognito authorizer security model.
|
|
65
|
+
|
|
66
|
+
NB: we only accept single user_pool_arn at the moment
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
DEFAULT_USER_POOL_ARN = "${cognito_user_pool_arn}"
|
|
70
|
+
|
|
71
|
+
def __init__(
|
|
72
|
+
self,
|
|
73
|
+
authorizer_name: str,
|
|
74
|
+
auto_error: bool = True,
|
|
75
|
+
user_pool_arn=None,
|
|
76
|
+
header_names=None,
|
|
77
|
+
):
|
|
78
|
+
"""Initialize with the authorizer name.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
authorizer_name (str): The name of the Cognito authorizer on AWS.
|
|
82
|
+
"""
|
|
83
|
+
self.user_pool_arn = user_pool_arn or CognitoAuthorizer.DEFAULT_USER_POOL_ARN
|
|
84
|
+
|
|
85
|
+
super().__init__(
|
|
86
|
+
authorizer_name,
|
|
87
|
+
"cognito_user_pools",
|
|
88
|
+
auto_error=auto_error,
|
|
89
|
+
header_names=header_names,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def _create_model(self):
|
|
93
|
+
return APIKey(
|
|
94
|
+
**{
|
|
95
|
+
"type": "apiKey",
|
|
96
|
+
"in": "header",
|
|
97
|
+
"name": self.scheme_name,
|
|
98
|
+
"x-amazon-apigateway-authtype": "cognito_user_pools",
|
|
99
|
+
"x-amazon-apigateway-authorizer": {
|
|
100
|
+
"type": self.authorizer_type,
|
|
101
|
+
"providerARNs": [self.user_pool_arn],
|
|
102
|
+
"authorizerResultTtlInSeconds": self.ttl,
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class APIKeyAuthorizer(AWSAuthorizer):
|
|
109
|
+
"""APIKey authorizers check the header field for a specific value.
|
|
110
|
+
|
|
111
|
+
TODO: x-amazon-apigateway-api-key-source implementation required.
|
|
112
|
+
https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-api-key-source.html
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
DEFAULT_HEADER_FIELD_NAME = "x-api-key"
|
|
116
|
+
|
|
117
|
+
def __init__(
|
|
118
|
+
self, *, authorizer_name: str, auto_error: bool = True, header_names: str = None
|
|
119
|
+
):
|
|
120
|
+
super().__init__(
|
|
121
|
+
authorizer_name, "token", auto_error=auto_error, header_names=header_names
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def _create_model(self):
|
|
125
|
+
raise NotImplementedError()
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class LambdaAuthorizer(AWSAuthorizer):
|
|
129
|
+
"""Lambda authorizers run custom authorization code
|
|
130
|
+
|
|
131
|
+
TODO: x-amazon-apigateway-api-key-source implementation required.
|
|
132
|
+
https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-api-key-source.html
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
def __init__(
|
|
136
|
+
self,
|
|
137
|
+
*,
|
|
138
|
+
authorizer_name: str,
|
|
139
|
+
auto_error: bool = True,
|
|
140
|
+
aws_lambda_uri: str = None,
|
|
141
|
+
aws_iam_role_arn: str = None,
|
|
142
|
+
**kwargs
|
|
143
|
+
):
|
|
144
|
+
assert aws_lambda_uri is not None
|
|
145
|
+
assert aws_iam_role_arn is not None
|
|
146
|
+
|
|
147
|
+
self.aws_lambda_uri = aws_lambda_uri
|
|
148
|
+
self.aws_iam_role_arn = aws_iam_role_arn
|
|
149
|
+
|
|
150
|
+
super().__init__(authorizer_name, "request", auto_error=auto_error, **kwargs)
|
|
151
|
+
|
|
152
|
+
def _create_model(self):
|
|
153
|
+
authorizer_params = {
|
|
154
|
+
"type": self.authorizer_type,
|
|
155
|
+
"authorizerUri": self.aws_lambda_uri,
|
|
156
|
+
"authorizerCredentials": self.aws_iam_role_arn,
|
|
157
|
+
"identityValidationExpression": "^x-[a-z]+",
|
|
158
|
+
"authorizerResultTtlInSeconds": self.ttl,
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if self.authorizer_type == "request":
|
|
162
|
+
assert (
|
|
163
|
+
self.header_names is not None
|
|
164
|
+
), "header_names is required when authorizer_type is 'request'"
|
|
165
|
+
|
|
166
|
+
mappings = [
|
|
167
|
+
".".join(("method", "request", "header", name))
|
|
168
|
+
for name in self.header_names
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
authorizer_params.update({"identitySource": ", ".join(mappings)})
|
|
172
|
+
print("%s: identity_source: '%s'" % ("auth", str(authorizer_params)))
|
|
173
|
+
|
|
174
|
+
return APIKey(
|
|
175
|
+
**{
|
|
176
|
+
"type": "apiKey",
|
|
177
|
+
"name": self.scheme_name,
|
|
178
|
+
"in": "header",
|
|
179
|
+
"x-amazon-apigateway-authtype": "custom",
|
|
180
|
+
"x-amazon-apigateway-authorizer": authorizer_params,
|
|
181
|
+
}
|
|
182
|
+
)
|