samcli/commands/local/lib/validators/lambda_auth_props.py (157 lines of code) (raw):
"""
Module to help validate Lambda Authorizer properties
"""
import logging
from abc import ABC, abstractmethod
from samcli.commands.local.cli_common.user_exceptions import InvalidSamTemplateException
from samcli.commands.local.lib.swagger.integration_uri import LambdaUri
from samcli.commands.local.lib.validators.identity_source_validator import IdentitySourceValidator
from samcli.local.apigw.authorizers.lambda_authorizer import LambdaAuthorizer
from samcli.local.apigw.route import Route
LOG = logging.getLogger(__name__)
class BaseLambdaAuthorizerValidator(ABC):
AUTHORIZER_TYPE = "Type"
AUTHORIZER_REST_API = "RestApiId"
AUTHORIZER_NAME = "Name"
AUTHORIZER_IDENTITY_SOURCE = "IdentitySource"
AUTHORIZER_VALIDATION = "IdentityValidationExpression"
AUTHORIZER_AUTHORIZER_URI = "AuthorizerUri"
@staticmethod
@abstractmethod
def validate(logical_id: str, resource: dict) -> bool:
"""
Validates if all the required properties for a Lambda Authorizer are present and valid.
Parameters
----------
logical_id: str
The logical ID of the authorizer
resource: dict
The resource dictionary for the authorizer containing the `Properties`
Returns
-------
bool
True if the `Properties` contains all the required key values
"""
@staticmethod
def _validate_common_properties(logical_id: str, properties: dict, type_key: str, api_key: str):
"""
Validates if the common required properties are present and valid, will raise an exception
if they are missing or invalid.
Parameters
----------
logical_id: str
The logical ID of the authorizer
properties: dict
The `Properties` dictionary for the authorizer
type_key: str
They authorizer type key to search for
api_key: str
The API Gateway reference key to search for
"""
authorizer_type = properties.get(type_key)
api_id = properties.get(api_key)
name = properties.get(BaseLambdaAuthorizerValidator.AUTHORIZER_NAME)
if not authorizer_type:
raise InvalidSamTemplateException(
f"Authorizer '{logical_id}' is missing the '{type_key}' "
"property, an Authorizer type must be defined."
)
if not api_id:
raise InvalidSamTemplateException(
f"Authorizer '{logical_id}' is missing the '{api_key}' " "property, this must be defined."
)
if not name:
raise InvalidSamTemplateException(
f"Authorizer '{logical_id}' is missing the '{BaseLambdaAuthorizerValidator.AUTHORIZER_NAME}' "
"property, the Name must be defined."
)
class LambdaAuthorizerV1Validator(BaseLambdaAuthorizerValidator):
@staticmethod
def validate(
logical_id: str,
resource: dict,
):
"""
Validates if all the required properties for a Lambda Authorizer V1 are present and valid.
Parameters
----------
logical_id: str
The logical ID of the authorizer
resource: dict
The resource dictionary for the authorizer containing the `Properties`
Returns
-------
bool
True if the `Properties` contains all the required key values
"""
properties = resource.get("Properties", {})
authorizer_type = properties.get(LambdaAuthorizerV1Validator.AUTHORIZER_TYPE, "")
authorizer_uri = properties.get(LambdaAuthorizerV1Validator.AUTHORIZER_AUTHORIZER_URI)
LambdaAuthorizerV1Validator._validate_common_properties(
logical_id,
properties,
LambdaAuthorizerV1Validator.AUTHORIZER_TYPE,
LambdaAuthorizerV1Validator.AUTHORIZER_REST_API,
)
# (lucashuy) AWS SAM CLI keeps references to types as lowercase strings
# while they are defined as uppercase strings in CFN
# this is to just validate that they are provided as upper case strings
if authorizer_type not in [type.upper() for type in LambdaAuthorizer.VALID_TYPES]:
LOG.warning(
"Authorizer '%s' with type '%s' is currently not supported. "
"Only Lambda Authorizers of type TOKEN and REQUEST are supported.",
logical_id,
authorizer_type,
)
return False
if not authorizer_uri:
raise InvalidSamTemplateException(
f"Authorizer '{logical_id}' is missing the '{LambdaAuthorizerV1Validator.AUTHORIZER_AUTHORIZER_URI}' "
"property, a valid Lambda ARN must be provided."
)
function_name = LambdaUri.get_function_name(authorizer_uri)
if not function_name:
LOG.warning(
"Was not able to resolve Lambda function ARN for Authorizer '%s'. "
"Double check the ARN format, or use more simple intrinsics.",
logical_id,
)
return False
identity_source_template = properties.get(LambdaAuthorizerV1Validator.AUTHORIZER_IDENTITY_SOURCE, None)
if identity_source_template is None and authorizer_type == LambdaAuthorizer.TOKEN.upper():
raise InvalidSamTemplateException(
f"Lambda Authorizer '{logical_id}' of type TOKEN, must have "
f"'{LambdaAuthorizerV1Validator.AUTHORIZER_IDENTITY_SOURCE}' of type string defined."
)
# (lucashuy) (regarding this if statement and the one below this)
# For API Gateway V1, an authorizer of type REQUEST can omit the identity sources
# if caching is enabled. Made the decision to not test this behaviour, and instead
# test if the it is a string.
if identity_source_template is not None and not isinstance(identity_source_template, str):
raise InvalidSamTemplateException(
f"Lambda Authorizer '{logical_id}' contains an invalid "
f"'{LambdaAuthorizerV1Validator.AUTHORIZER_IDENTITY_SOURCE}', "
"it must be a comma-separated string."
)
validation_expression = properties.get(LambdaAuthorizerV1Validator.AUTHORIZER_VALIDATION)
if authorizer_type == LambdaAuthorizer.REQUEST.upper() and validation_expression:
raise InvalidSamTemplateException(
"Lambda Authorizer '%s' has '%s' property defined, but validation is only "
"supported on TOKEN type authorizers." % (logical_id, LambdaAuthorizerV1Validator.AUTHORIZER_VALIDATION)
)
return True
class LambdaAuthorizerV2Validator(BaseLambdaAuthorizerValidator):
AUTHORIZER_V2_TYPE = "AuthorizerType"
AUTHORIZER_V2_API = "ApiId"
AUTHORIZER_V2_PAYLOAD = "AuthorizerPayloadFormatVersion"
AUTHORIZER_V2_SIMPLE_RESPONSE = "EnableSimpleResponses"
@staticmethod
def validate(
logical_id: str,
resource: dict,
):
"""
Validates if all the required properties for a Lambda Authorizer V2 are present and valid.
Parameters
----------
logical_id: str
The logical ID of the authorizer
resource: dict
The resource dictionary for the authorizer containing the `Properties`
Returns
-------
bool
True if the `Properties` contains all the required key values
"""
properties = resource.get("Properties", {})
authorizer_type = properties.get(LambdaAuthorizerV2Validator.AUTHORIZER_V2_TYPE, "")
authorizer_uri = properties.get(LambdaAuthorizerV2Validator.AUTHORIZER_AUTHORIZER_URI)
LambdaAuthorizerV2Validator._validate_common_properties(
logical_id,
properties,
LambdaAuthorizerV2Validator.AUTHORIZER_V2_TYPE,
LambdaAuthorizerV2Validator.AUTHORIZER_V2_API,
)
# (lucashuy) AWS SAM CLI keeps references to types as lowercase strings
# while they are defined as uppercase strings in CFN
# this is to just validate that they are provided as upper case strings
if authorizer_type != LambdaAuthorizer.REQUEST.upper():
LOG.warning(
"Authorizer '%s' with type '%s' is currently not supported. "
"Only Lambda Authorizers of type REQUEST are supported for API Gateway V2.",
logical_id,
authorizer_type,
)
return False
if not authorizer_uri:
raise InvalidSamTemplateException(
f"Authorizer '{logical_id}' is missing the '{LambdaAuthorizerV2Validator.AUTHORIZER_AUTHORIZER_URI}' "
"property, a valid Lambda ARN must be provided."
)
function_name = LambdaUri.get_function_name(authorizer_uri)
if not function_name:
LOG.warning(
"Was not able to resolve Lambda function ARN for Authorizer '%s'. "
"Double check the ARN format, or use more simple intrinsics.",
logical_id,
)
return False
identity_sources = properties.get(LambdaAuthorizerV2Validator.AUTHORIZER_IDENTITY_SOURCE, None)
if not isinstance(identity_sources, list):
raise InvalidSamTemplateException(
f"Lambda Authorizer '{logical_id}' must have "
f"'{LambdaAuthorizerV2Validator.AUTHORIZER_IDENTITY_SOURCE}' of type list defined."
)
for identity_source in identity_sources:
if not IdentitySourceValidator.validate_identity_source(identity_source, Route.HTTP):
raise InvalidSamTemplateException(
f"Lambda Authorizer {logical_id} does not contain valid identity sources.", Route.HTTP
)
payload_version = properties.get(LambdaAuthorizerV2Validator.AUTHORIZER_V2_PAYLOAD)
if payload_version not in [None, *LambdaAuthorizer.PAYLOAD_VERSIONS]:
raise InvalidSamTemplateException(
f"Lambda Authorizer '{logical_id}' contains an invalid "
f"'{LambdaAuthorizerV2Validator.AUTHORIZER_V2_PAYLOAD}'"
", it must be set to '1.0' or '2.0'"
)
simple_responses = properties.get(LambdaAuthorizerV2Validator.AUTHORIZER_V2_SIMPLE_RESPONSE, False)
if payload_version == LambdaAuthorizer.PAYLOAD_V1 and simple_responses:
raise InvalidSamTemplateException(
f"'{LambdaAuthorizerV2Validator.AUTHORIZER_V2_SIMPLE_RESPONSE}' is only supported for '2.0' "
f"payload format versions for Lambda Authorizer '{logical_id}'."
)
return True