From bc2a961b381e4551b4cb62bb7bed8f4289921e74 Mon Sep 17 00:00:00 2001 From: Vichym Date: Fri, 27 Mar 2026 17:46:15 -0700 Subject: [PATCH 1/2] refactor: improve HttpApi authorizer validation --- samtranslator/model/apigatewayv2.py | 78 +++++++++++++---------------- tests/model/test_api_v2.py | 21 ++++---- 2 files changed, 46 insertions(+), 53 deletions(-) diff --git a/samtranslator/model/apigatewayv2.py b/samtranslator/model/apigatewayv2.py index cb9f363168..4badc1c57e 100644 --- a/samtranslator/model/apigatewayv2.py +++ b/samtranslator/model/apigatewayv2.py @@ -165,54 +165,48 @@ def _get_auth_type(self) -> str: return "JWT" return "REQUEST" + # Maps each authorizer type to the set of properties it accepts + ALLOWED_PROPERTIES = { + "JWT": {"authorization_scopes", "jwt_configuration", "id_source"}, + "REQUEST": { + "function_arn", + "function_invoke_role", + "identity", + "authorizer_payload_format_version", + "enable_simple_responses", + "enable_function_default_permissions", + }, + "AWS_IAM": set(), + } + + # Maps internal attr name to (display name, error hint) + PROPERTY_DISPLAY = { + "authorization_scopes": ("AuthorizationScopes", "OAuth2 Authorizer"), + "jwt_configuration": ("JwtConfiguration", "OAuth2 Authorizer"), + "id_source": ( + "IdentitySource", + "OAuth2 Authorizer. For Lambda Authorizer, use the 'Identity' property instead", + ), + "function_arn": ("FunctionArn", "Lambda Authorizer"), + "function_invoke_role": ("FunctionInvokeRole", "Lambda Authorizer"), + "identity": ("Identity", "Lambda Authorizer"), + "authorizer_payload_format_version": ("AuthorizerPayloadFormatVersion", "Lambda Authorizer"), + "enable_simple_responses": ("EnableSimpleResponses", "Lambda Authorizer"), + "enable_function_default_permissions": ("EnableFunctionDefaultPermissions", "Lambda Authorizer"), + } + def _validate_input_parameters(self) -> None: authorizer_type = self._get_auth_type() if self.authorization_scopes is not None and not isinstance(self.authorization_scopes, list): raise InvalidResourceException(self.api_logical_id, "AuthorizationScopes must be a list.") - if self.authorization_scopes is not None and not authorizer_type == "JWT": - raise InvalidResourceException( - self.api_logical_id, "AuthorizationScopes must be defined only for OAuth2 Authorizer." - ) - - if self.jwt_configuration is not None and not authorizer_type == "JWT": - raise InvalidResourceException( - self.api_logical_id, "JwtConfiguration must be defined only for OAuth2 Authorizer." - ) - - if self.id_source is not None and not authorizer_type == "JWT": - raise InvalidResourceException( - self.api_logical_id, "IdentitySource must be defined only for OAuth2 Authorizer." - ) - - if self.function_arn is not None and not authorizer_type == "REQUEST": - raise InvalidResourceException( - self.api_logical_id, "FunctionArn must be defined only for Lambda Authorizer." - ) - - if self.function_invoke_role is not None and not authorizer_type == "REQUEST": - raise InvalidResourceException( - self.api_logical_id, "FunctionInvokeRole must be defined only for Lambda Authorizer." - ) - - if self.identity is not None and not authorizer_type == "REQUEST": - raise InvalidResourceException(self.api_logical_id, "Identity must be defined only for Lambda Authorizer.") - - if self.authorizer_payload_format_version is not None and not authorizer_type == "REQUEST": - raise InvalidResourceException( - self.api_logical_id, "AuthorizerPayloadFormatVersion must be defined only for Lambda Authorizer." - ) - - if self.enable_simple_responses is not None and not authorizer_type == "REQUEST": - raise InvalidResourceException( - self.api_logical_id, "EnableSimpleResponses must be defined only for Lambda Authorizer." - ) - - if self.enable_function_default_permissions is not None and authorizer_type != "REQUEST": - raise InvalidResourceException( - self.api_logical_id, "EnableFunctionDefaultPermissions must be defined only for Lambda Authorizer." - ) + allowed = self.ALLOWED_PROPERTIES.get(authorizer_type, set()) + for attr, (display_name, allowed_for) in self.PROPERTY_DISPLAY.items(): + if getattr(self, attr) is not None and attr not in allowed: + raise InvalidResourceException( + self.api_logical_id, f"{display_name} is only supported for {allowed_for}." + ) def _validate_jwt_authorizer(self) -> None: if not self.jwt_configuration: diff --git a/tests/model/test_api_v2.py b/tests/model/test_api_v2.py index 9820986afa..b71f3c6d03 100644 --- a/tests/model/test_api_v2.py +++ b/tests/model/test_api_v2.py @@ -64,7 +64,7 @@ def test_create_authorizer_fails_with_authorization_scopes_non_oauth2(self): self.assertEqual( e.value.message, "Resource with id [logicalId] is invalid. " - + "AuthorizationScopes must be defined only for OAuth2 Authorizer.", + + "AuthorizationScopes is only supported for OAuth2 Authorizer.", ) @mock.patch( @@ -79,8 +79,7 @@ def test_create_authorizer_fails_with_jtw_configuration_non_oauth2(self): ) self.assertEqual( e.value.message, - "Resource with id [logicalId] is invalid. " - + "JwtConfiguration must be defined only for OAuth2 Authorizer.", + "Resource with id [logicalId] is invalid. " + "JwtConfiguration is only supported for OAuth2 Authorizer.", ) def test_create_authorizer_fails_with_id_source_non_oauth2(self): @@ -92,7 +91,8 @@ def test_create_authorizer_fails_with_id_source_non_oauth2(self): ) self.assertEqual( e.value.message, - "Resource with id [logicalId] is invalid. " + "IdentitySource must be defined only for OAuth2 Authorizer.", + "Resource with id [logicalId] is invalid. " + "IdentitySource is only supported for OAuth2 Authorizer." + " For Lambda Authorizer, use the 'Identity' property instead.", ) def test_create_authorizer_fails_with_function_arn_non_lambda(self): @@ -106,7 +106,7 @@ def test_create_authorizer_fails_with_function_arn_non_lambda(self): ) self.assertEqual( e.value.message, - "Resource with id [logicalId] is invalid. " + "FunctionArn must be defined only for Lambda Authorizer.", + "Resource with id [logicalId] is invalid. " + "FunctionArn is only supported for Lambda Authorizer.", ) def test_create_authorizer_fails_with_function_invoke_role_non_lambda(self): @@ -120,8 +120,7 @@ def test_create_authorizer_fails_with_function_invoke_role_non_lambda(self): ) self.assertEqual( e.value.message, - "Resource with id [logicalId] is invalid. " - + "FunctionInvokeRole must be defined only for Lambda Authorizer.", + "Resource with id [logicalId] is invalid. " + "FunctionInvokeRole is only supported for Lambda Authorizer.", ) def test_create_authorizer_fails_with_identity_non_lambda(self): @@ -135,7 +134,7 @@ def test_create_authorizer_fails_with_identity_non_lambda(self): ) self.assertEqual( e.value.message, - "Resource with id [logicalId] is invalid. " + "Identity must be defined only for Lambda Authorizer.", + "Resource with id [logicalId] is invalid. " + "Identity is only supported for Lambda Authorizer.", ) def test_create_authorizer_fails_with_authorizer_payload_format_version_non_lambda(self): @@ -150,7 +149,7 @@ def test_create_authorizer_fails_with_authorizer_payload_format_version_non_lamb self.assertEqual( e.value.message, "Resource with id [logicalId] is invalid. " - + "AuthorizerPayloadFormatVersion must be defined only for Lambda Authorizer.", + + "AuthorizerPayloadFormatVersion is only supported for Lambda Authorizer.", ) def test_create_authorizer_fails_with_enable_simple_responses_non_lambda(self): @@ -165,7 +164,7 @@ def test_create_authorizer_fails_with_enable_simple_responses_non_lambda(self): self.assertEqual( e.value.message, "Resource with id [logicalId] is invalid. " - + "EnableSimpleResponses must be defined only for Lambda Authorizer.", + + "EnableSimpleResponses is only supported for Lambda Authorizer.", ) def test_create_authorizer_fails_with_enable_function_default_permissions_non_lambda(self): @@ -180,7 +179,7 @@ def test_create_authorizer_fails_with_enable_function_default_permissions_non_la self.assertEqual( e.value.message, "Resource with id [logicalId] is invalid. " - + "EnableFunctionDefaultPermissions must be defined only for Lambda Authorizer.", + + "EnableFunctionDefaultPermissions is only supported for Lambda Authorizer.", ) @mock.patch( From 7ee65cba1a032a85113d9f1c9f1e36212ed9a05b Mon Sep 17 00:00:00 2001 From: Vichym Date: Mon, 30 Mar 2026 16:40:52 -0700 Subject: [PATCH 2/2] update test --- .../translator/output/error_http_api_invalid_lambda_auth.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/translator/output/error_http_api_invalid_lambda_auth.json b/tests/translator/output/error_http_api_invalid_lambda_auth.json index d4069a8928..6e636d4459 100644 --- a/tests/translator/output/error_http_api_invalid_lambda_auth.json +++ b/tests/translator/output/error_http_api_invalid_lambda_auth.json @@ -9,7 +9,7 @@ "Resource with id [MyApi3] is invalid. ", "Property 'Authorizers.LambdaAuth.EnableFunctionDefaultPermissions' should be a boolean. ", "Resource with id [MyApi4] is invalid. ", - "EnableFunctionDefaultPermissions must be defined only for Lambda Authorizer." + "EnableFunctionDefaultPermissions is only supported for Lambda Authorizer." ], - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 4. Resource with id [MyApi1] is invalid. LambdaAuth Lambda Authorizer must define 'AuthorizerPayloadFormatVersion'. Resource with id [MyApi2] is invalid. LambdaAuth Lambda Authorizer must define 'FunctionArn'. Resource with id [MyApi3] is invalid. Property 'Authorizers.LambdaAuth.EnableFunctionDefaultPermissions' should be a boolean. Resource with id [MyApi4] is invalid. EnableFunctionDefaultPermissions must be defined only for Lambda Authorizer." + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 4. Resource with id [MyApi1] is invalid. LambdaAuth Lambda Authorizer must define 'AuthorizerPayloadFormatVersion'. Resource with id [MyApi2] is invalid. LambdaAuth Lambda Authorizer must define 'FunctionArn'. Resource with id [MyApi3] is invalid. Property 'Authorizers.LambdaAuth.EnableFunctionDefaultPermissions' should be a boolean. Resource with id [MyApi4] is invalid. EnableFunctionDefaultPermissions is only supported for Lambda Authorizer." }