Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -939,11 +939,18 @@ to change Zappa's behavior. Use these at your own risk!
"iam_authorization": false, // optional, use IAM to require request signing. Default false. Note that enabling this will override the authorizer configuration.
"include": ["your_special_library_to_load_at_handler_init"], // load special libraries into PYTHONPATH at handler init that certain modules cannot find on path
"authorizer": {
"type": "REQUEST", // Authorizer type, REQUEST or TOKEN (Default 'TOKEN')
"function": "your_module.your_auth_function", // Local function to run for token validation. For more information about the function see below.
"arn": "arn:aws:lambda:<region>:<account_id>:function:<function_name>", // Existing Lambda function to run for token validation.
"result_ttl": 300, // Optional. Default 300. The time-to-live (TTL) period, in seconds, that specifies how long API Gateway caches authorizer results. Currently, the maximum TTL value is 3600 seconds.
"token_header": "Authorization", // Optional. Default 'Authorization'. The name of a custom authorization header containing the token that clients submit as part of their requests.
"validation_expression": "^Bearer \\w+$", // Optional. A validation expression for the incoming token, specify a regular expression.
"identity_sources": { // Optional. The names of the custom request expressions destined for the authorizer.
"headers": ["Authorization", "Host"],
"query_strings": ["token"],
"stage_variables": ["test"],
"contexts": ["principalId"],
}
},
"keep_warm": true, // Create CloudWatch events to keep the server warm. Default true. To remove, set to false and then `unschedule`.
"keep_warm_expression": "rate(4 minutes)", // How often to execute the keep-warm, in cron and rate format. Default 4 minutes.
Expand Down
55 changes: 55 additions & 0 deletions tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@ def test_create_api_gateway_routes_with_different_auth_methods(self):

# Authorizer and IAM
authorizer = {
"type": "TOKEN",
"function": "runapi.authorization.gateway_authorizer.evaluate_token",
"result_ttl": 300,
"token_header": "Authorization",
Expand Down Expand Up @@ -469,6 +470,60 @@ def test_create_api_gateway_routes_with_different_auth_methods(self):
parsable_template["Resources"]["Authorizer"]["Properties"]["AuthorizerUri"],
)

# Authorizer of type request with identity sources
authorizer = {
"type": "REQUEST",
"function": "runapi.authorization.gateway_authorizer.evaluate_token",
"result_ttl": 300,
"identity_sources": {
"headers": ["Authorization"],
"query_strings": ["token"],
"stage_variables": ["test"],
"contexts": ["principalId"],
}
}
z.create_stack_template(lambda_arn, "helloworld", False, False, authorizer)
parsable_template = json.loads(z.cf_template.to_json())
self.assertEqual(
"CUSTOM",
parsable_template["Resources"]["GET0"]["Properties"]["AuthorizationType"],
)
self.assertEqual(
"CUSTOM",
parsable_template["Resources"]["GET1"]["Properties"]["AuthorizationType"],
)
self.assertEqual(
"REQUEST", parsable_template["Resources"]["Authorizer"]["Properties"]["Type"]
)
self.assertEqual(
"method.request.header.Authorization,method.request.querystring.token,method.stageVariables.test,method.context.principalId",
parsable_template["Resources"]["Authorizer"]["Properties"]["IdentitySource"]
)

# Authorizer of type request without identity sources
authorizer = {
"type": "REQUEST",
"function": "runapi.authorization.gateway_authorizer.evaluate_token",
"result_ttl": 300,
}
z.create_stack_template(lambda_arn, "helloworld", False, False, authorizer)
parsable_template = json.loads(z.cf_template.to_json())
self.assertEqual(
"CUSTOM",
parsable_template["Resources"]["GET0"]["Properties"]["AuthorizationType"],
)
self.assertEqual(
"CUSTOM",
parsable_template["Resources"]["GET1"]["Properties"]["AuthorizationType"],
)
self.assertEqual(
"REQUEST", parsable_template["Resources"]["Authorizer"]["Properties"]["Type"]
)
self.assertEqual(
"",
parsable_template["Resources"]["Authorizer"]["Properties"]["IdentitySource"]
)

def test_policy_json(self):
# ensure the policy docs are valid JSON
json.loads(ASSUME_POLICY)
Expand Down
60 changes: 59 additions & 1 deletion tests/tests_placebo.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,14 +433,72 @@ def test_handler(self, session):
}
self.assertEqual("AWS SQS EVENT", lh.handler(event, None))

# Test Authorizer event
# Test Authorizer event of type TOKEN
event = {
"authorizationToken": "hubtoken1",
"methodArn": "arn:aws:execute-api:us-west-2:1234:xxxxx/dev/GET/v1/endpoint/param",
"type": "TOKEN",
}
self.assertEqual("AUTHORIZER_EVENT", lh.handler(event, None))

# Test Authorizer event of type REQUEST
event = {
"type": "REQUEST",
"methodArn": "arn:aws:execute-api:us-west-2:1234:xxxxx/dev/GET/v1/endpoint/param",
"resource": "/",
"path": "/",
"httpMethod": "GET",
"headers": {
"Authorization": "Basic dXNlcm5hbWU6cGFzc3dvcmQ=",
"Host": "example.com"
},
"multiValueHeaders": {
"Authorization": [
"Basic dXNlcm5hbWU6cGFzc3dvcmQ="
],
"Host": [
"example.com"
]
},
"queryStringParameters": {},
"multiValueQueryStringParameters": {},
"pathParameters": {},
"stageVariables": {},
"requestContext": {
"resourceId": "test-invoke-resource-id",
"resourcePath": "/",
"httpMethod": "GET",
"extendedRequestId": "ODtjMEaurPEFpbQ=",
"requestTime": "24/Feb/2022: 17: 39: 45 +0000",
"path": "/",
"accountId": "429480868624",
"protocol": "HTTP/1.1",
"stage": "test-invoke-stage",
"domainPrefix": "testPrefix",
"requestTimeEpoch": 1645724385013,
"requestId": "13e8a7e1-1b24-467a-afd1-854d7268db1b",
"identity": {
"cognitoIdentityPoolId": None,
"cognitoIdentityId": None,
"apiKey": "test-invoke-api-key",
"principalOrgId": None,
"cognitoAuthenticationType": None,
"userArn": "arn:aws:iam::fooo:user/my.username",
"apiKeyId": "test-invoke-api-key-id",
"userAgent": "aws-internal/3 aws-sdk-java/1.12.159 Linux/5.4.172-100.336.amzn2int.x86_64 OpenJDK_64-Bit_Server_VM/25.322-b06 java/1.8.0_322 vendor/Oracle_Corporation cfg/retry-mode/standard",
"accountId": "429480868624",
"caller": "AIDAWH7YN7MIM5EMI2SCJ",
"sourceIp": "test-invoke-source-ip",
"accessKey": "ASIAWH7YN7MIOMF3BO7W",
"cognitoAuthenticationProvider": None,
"user": None
},
"domainName": "testPrefix.testDomainName",
"apiId": "nyfueqhql3"
}
}
self.assertEqual("AUTHORIZER_EVENT", lh.handler(event, None))

# Ensure Zappa does return 401 if no function was defined.
lh.settings.AUTHORIZER_FUNCTION = None
with self.assertRaisesRegexp(Exception, "Unauthorized"):
Expand Down
35 changes: 33 additions & 2 deletions zappa/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1789,10 +1789,39 @@ def create_authorizer(self, restapi, uri, authorizer):
if authorizer_type == "TOKEN":
if not self.credentials_arn:
self.get_credentials_arn()
authorizer_resource.AuthorizerResultTtlInSeconds = authorizer.get("result_ttl", 300)
authorizer_resource.AuthorizerResultTtlInSeconds = authorizer.get(
"result_ttl", 300
)
authorizer_resource.IdentitySource = (
"method.request.header.%s" % authorizer.get("token_header", "Authorization")
)
authorizer_resource.AuthorizerCredentials = self.credentials_arn
if authorizer_type == "COGNITO_USER_POOLS":
authorizer_resource.ProviderARNs = authorizer.get("provider_arns")
if authorizer_type == "REQUEST":
if not self.credentials_arn:
self.get_credentials_arn()
authorizer_resource.AuthorizerResultTtlInSeconds = authorizer.get(
"result_ttl", 300
)
authorizer_resource.IdentitySource = ""
identity_sources = authorizer.get("identity_sources", {})
for source_key in identity_sources:
if source_key == "headers":
for header in identity_sources[source_key]:
authorizer_resource.IdentitySource += "method.request.header.%s," % header
elif source_key == "query_strings":
for query_string in identity_sources[source_key]:
authorizer_resource.IdentitySource += "method.request.querystring.%s," % query_string
elif source_key == "stage_variables":
for stage_variable in identity_sources[source_key]:
authorizer_resource.IdentitySource += "method.stageVariables.%s," % stage_variable
elif source_key == "contexts":
for context in identity_sources[source_key]:
authorizer_resource.IdentitySource += "method.context.%s," % context

if len(authorizer_resource.IdentitySource) > 1 and authorizer_resource.IdentitySource[-1] == ',':
authorizer_resource.IdentitySource = authorizer_resource.IdentitySource[:-1]

self.cf_api_resources.append(authorizer_resource.title)
self.cf_template.add_resource(authorizer_resource)
Expand Down Expand Up @@ -2261,7 +2290,9 @@ def create_stack_template(
elif iam_authorization:
auth_type = "AWS_IAM"
elif authorizer:
auth_type = authorizer.get("type", "CUSTOM")
auth_type = authorizer.get("type", "TOKEN").upper()
if auth_type in ["TOKEN", "REQUEST"]:
auth_type = "CUSTOM"

# build a fresh template
self.cf_template = troposphere.Template()
Expand Down
2 changes: 1 addition & 1 deletion zappa/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ def handler(self, event, context):
return result

# This is an API Gateway authorizer event
elif event.get("type") == "TOKEN":
elif event.get("type") in ["TOKEN", "REQUEST"]:
whole_function = self.settings.AUTHORIZER_FUNCTION
if whole_function:
app_function = self.import_module_and_get_function(whole_function)
Expand Down