""" 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