diff --git a/localstack-core/localstack/services/apigateway/helpers.py b/localstack-core/localstack/services/apigateway/helpers.py index 8750da66e3bb0..ddb2c1784f942 100644 --- a/localstack-core/localstack/services/apigateway/helpers.py +++ b/localstack-core/localstack/services/apigateway/helpers.py @@ -4,7 +4,7 @@ import json import logging from datetime import datetime -from typing import List, Optional, Union +from typing import List, Optional, TypedDict, Union from urllib import parse as urlparse from jsonpatch import apply_patch @@ -91,6 +91,11 @@ class OpenAPIExt: TAG_VALUE = "x-amazon-apigateway-tag-value" +class AuthorizerConfig(TypedDict): + authorizer: Authorizer + authorization_scopes: Optional[list[str]] + + # TODO: make the CRUD operations in this file generic for the different model types (authorizes, validators, ...) @@ -564,14 +569,14 @@ def create_authorizers(security_schemes: dict) -> None: authorizers[security_scheme_name] = authorizer - def get_authorizer(path_payload: dict) -> Optional[Authorizer]: + def get_authorizer(path_payload: dict) -> Optional[AuthorizerConfig]: if not (security_schemes := path_payload.get("security")): return None for security_scheme in security_schemes: - for security_scheme_name in security_scheme.keys(): + for security_scheme_name, scopes in security_scheme.items(): if authorizer := authorizers.get(security_scheme_name): - return authorizer + return AuthorizerConfig(authorizer=authorizer, authorization_scopes=scopes) def get_or_create_path(abs_path: str, base_path: str): parts = abs_path.rstrip("/").replace("//", "/").split("/") @@ -815,7 +820,7 @@ def create_method_resource(child, method, method_schema): kwargs = {} if authorizer := get_authorizer(method_schema) or default_authorizer: - method_authorizer = authorizer or default_authorizer + method_authorizer = authorizer["authorizer"] # override the authorizer_type if it's a TOKEN or REQUEST to CUSTOM if (authorizer_type := method_authorizer["type"]) in ("TOKEN", "REQUEST"): authorization_type = "CUSTOM" @@ -824,6 +829,9 @@ def create_method_resource(child, method, method_schema): kwargs["authorizer_id"] = method_authorizer["id"] + if authorization_scopes := authorizer.get("authorization_scopes"): + kwargs["authorization_scopes"] = authorization_scopes + return child.add_method( method, api_key_required=api_key_required, diff --git a/tests/aws/files/openapi.cognito-auth.json b/tests/aws/files/openapi.cognito-auth.json index 7132880092fae..416bf3f274aef 100644 --- a/tests/aws/files/openapi.cognito-auth.json +++ b/tests/aws/files/openapi.cognito-auth.json @@ -6,6 +6,75 @@ "version": "1.0" }, "paths": { + "/default-no-scope": { + "get": { + "security": [ + {"cognito-test-identity-source": []} + ], + "responses": { + "200": { + "description": "200 response" + } + }, + "x-amazon-apigateway-integration": { + "responses": { + "default": { + "statusCode": "200" + } + }, + "uri": "http://petstore-demo-endpoint.execute-api.com/petstore/pets", + "passthroughBehavior": "when_no_match", + "httpMethod": "GET", + "type": "http" + } + } + }, + "/default-scope-override": { + "get": { + "security": [ + {"cognito-test-identity-source": ["openid"]} + ], + "responses": { + "200": { + "description": "200 response" + } + }, + "x-amazon-apigateway-integration": { + "responses": { + "default": { + "statusCode": "200" + } + }, + "uri": "http://petstore-demo-endpoint.execute-api.com/petstore/pets", + "passthroughBehavior": "when_no_match", + "httpMethod": "GET", + "type": "http" + } + } + }, + "/non-default-authorizer": { + "get": { + "security": [ + {"extra-test-identity-source": ["email", "openid"]} + ], + "responses": { + "200": { + "description": "200 response" + } + }, + "x-amazon-apigateway-integration": { + "responses": { + "default": { + "statusCode": "200" + } + }, + "uri": "http://petstore-demo-endpoint.execute-api.com/petstore/pets", + "passthroughBehavior": "when_no_match", + "httpMethod": "GET", + "type": "http" + } + } + }, "/pets": { "get": { "operationId": "GET HTTP", @@ -66,6 +135,18 @@ "${cognito_pool_arn}" ] } + }, + "extra-test-identity-source": { + "type": "apiKey", + "name": "TestHeaderAuth", + "in": "header", + "x-amazon-apigateway-authtype": "cognito_user_pools", + "x-amazon-apigateway-authorizer": { + "type": "cognito_user_pools", + "providerARNs": [ + "${cognito_pool_arn}" + ] + } } }, "schemas": { @@ -93,5 +174,6 @@ } } } - } + }, + "security": [{"cognito-test-identity-source": ["email"]}] } diff --git a/tests/aws/services/apigateway/test_apigateway_import.py b/tests/aws/services/apigateway/test_apigateway_import.py index aa91c51b5be81..3511814e1c101 100644 --- a/tests/aws/services/apigateway/test_apigateway_import.py +++ b/tests/aws/services/apigateway/test_apigateway_import.py @@ -842,6 +842,11 @@ def test_import_with_http_method_integration( apigw_snapshot_imported_resources(rest_api_id=rest_api_id, resources=response) @pytest.mark.no_apigw_snap_transformers + @markers.snapshot.skip_snapshot_verify( + paths=[ + "$.resources.items..resourceMethods.GET", # AWS does not show them after import + ] + ) @markers.aws.validated def test_import_with_cognito_auth_identity_source( self, @@ -856,10 +861,10 @@ def test_import_with_cognito_auth_identity_source( [ snapshot.transform.jsonpath("$.import-swagger.id", value_replacement="rest-id"), snapshot.transform.jsonpath( - "$.import-swagger.rootResourceId", value_replacement="root-resource-id" + "$.resources.items..id", value_replacement="resource-id" ), snapshot.transform.jsonpath( - "$.get-authorizers.items..id", value_replacement="authorizer-id" + "$.get-authorizers..id", value_replacement="authorizer-id" ), ] ) @@ -874,6 +879,13 @@ def test_import_with_cognito_auth_identity_source( rest_api_id = response["id"] - # assert that are no multiple authorizers authorizers = aws_client.apigateway.get_authorizers(restApiId=rest_api_id) - snapshot.match("get-authorizers", authorizers) + snapshot.match("get-authorizers", sorted(authorizers["items"], key=lambda x: x["name"])) + + response = aws_client.apigateway.get_resources(restApiId=rest_api_id) + response["items"] = sorted(response["items"], key=itemgetter("path")) + snapshot.match("resources", response) + + # this fixture will iterate over every resource and match its method, methodResponse, integration and + # integrationResponse + apigw_snapshot_imported_resources(rest_api_id=rest_api_id, resources=response) diff --git a/tests/aws/services/apigateway/test_apigateway_import.snapshot.json b/tests/aws/services/apigateway/test_apigateway_import.snapshot.json index d78cd9cf8699b..c232f286ef439 100644 --- a/tests/aws/services/apigateway/test_apigateway_import.snapshot.json +++ b/tests/aws/services/apigateway/test_apigateway_import.snapshot.json @@ -4810,7 +4810,7 @@ } }, "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_cognito_auth_identity_source": { - "recorded-date": "05-11-2024, 11:37:35", + "recorded-date": "26-11-2024, 21:33:17", "recorded-content": { "import-swagger": { "apiKeySource": "HEADER", @@ -4824,30 +4824,355 @@ }, "id": "", "name": "Example Pet Store", - "rootResourceId": "", + "rootResourceId": "", "version": "1.0", "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 201 } }, - "get-authorizers": { + "get-authorizers": [ + { + "id": "", + "name": "cognito-test-identity-source", + "type": "COGNITO_USER_POOLS", + "providerARNs": [ + "arn::cognito-idp::111111111111:userpool/_ABC123" + ], + "authType": "cognito_user_pools", + "identitySource": "method.request.header.TestHeaderAuth" + }, + { + "id": "", + "name": "extra-test-identity-source", + "type": "COGNITO_USER_POOLS", + "providerARNs": [ + "arn::cognito-idp::111111111111:userpool/_ABC123" + ], + "authType": "cognito_user_pools", + "identitySource": "method.request.header.TestHeaderAuth" + } + ], + "resources": { "items": [ { - "authType": "cognito_user_pools", - "id": "", - "identitySource": "method.request.header.TestHeaderAuth", - "name": "cognito-test-identity-source", - "providerARNs": [ - "arn::cognito-idp::111111111111:userpool/_ABC123" - ], - "type": "COGNITO_USER_POOLS" + "id": "", + "path": "/" + }, + { + "id": "", + "parentId": "", + "path": "/default-no-scope", + "pathPart": "default-no-scope", + "resourceMethods": { + "GET": {} + } + }, + { + "id": "", + "parentId": "", + "path": "/default-scope-override", + "pathPart": "default-scope-override", + "resourceMethods": { + "GET": {} + } + }, + { + "id": "", + "parentId": "", + "path": "/non-default-authorizer", + "pathPart": "non-default-authorizer", + "resourceMethods": { + "GET": {} + } + }, + { + "id": "", + "parentId": "", + "path": "/pets", + "pathPart": "pets", + "resourceMethods": { + "GET": {} + } + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "method-default-no-scope-get": { + "apiKeyRequired": false, + "authorizationType": "COGNITO_USER_POOLS", + "authorizerId": "", + "httpMethod": "GET", + "methodIntegration": { + "cacheKeyParameters": [], + "cacheNamespace": "", + "connectionType": "INTERNET", + "httpMethod": "GET", + "integrationResponses": { + "200": { + "statusCode": "200" + } + }, + "passthroughBehavior": "WHEN_NO_MATCH", + "timeoutInMillis": 29000, + "type": "HTTP", + "uri": "http://petstore-demo-endpoint.execute-api.com/petstore/pets" + }, + "methodResponses": { + "200": { + "statusCode": "200" + } + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "method-response-default-no-scope-get": { + "statusCode": "200", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "integration-default-no-scope-get": { + "cacheKeyParameters": [], + "cacheNamespace": "", + "connectionType": "INTERNET", + "httpMethod": "GET", + "integrationResponses": { + "200": { + "statusCode": "200" } + }, + "passthroughBehavior": "WHEN_NO_MATCH", + "timeoutInMillis": 29000, + "type": "HTTP", + "uri": "http://petstore-demo-endpoint.execute-api.com/petstore/pets", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "integration-response-default-no-scope-get": { + "statusCode": "200", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "method-default-scope-override-get": { + "apiKeyRequired": false, + "authorizationScopes": [ + "openid" ], + "authorizationType": "COGNITO_USER_POOLS", + "authorizerId": "", + "httpMethod": "GET", + "methodIntegration": { + "cacheKeyParameters": [], + "cacheNamespace": "", + "connectionType": "INTERNET", + "httpMethod": "GET", + "integrationResponses": { + "200": { + "statusCode": "200" + } + }, + "passthroughBehavior": "WHEN_NO_MATCH", + "timeoutInMillis": 29000, + "type": "HTTP", + "uri": "http://petstore-demo-endpoint.execute-api.com/petstore/pets" + }, + "methodResponses": { + "200": { + "statusCode": "200" + } + }, "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } + }, + "method-response-default-scope-override-get": { + "statusCode": "200", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "integration-default-scope-override-get": { + "cacheKeyParameters": [], + "cacheNamespace": "", + "connectionType": "INTERNET", + "httpMethod": "GET", + "integrationResponses": { + "200": { + "statusCode": "200" + } + }, + "passthroughBehavior": "WHEN_NO_MATCH", + "timeoutInMillis": 29000, + "type": "HTTP", + "uri": "http://petstore-demo-endpoint.execute-api.com/petstore/pets", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "integration-response-default-scope-override-get": { + "statusCode": "200", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "method-non-default-authorizer-get": { + "apiKeyRequired": false, + "authorizationScopes": [ + "email", + "openid" + ], + "authorizationType": "COGNITO_USER_POOLS", + "authorizerId": "", + "httpMethod": "GET", + "methodIntegration": { + "cacheKeyParameters": [], + "cacheNamespace": "", + "connectionType": "INTERNET", + "httpMethod": "GET", + "integrationResponses": { + "200": { + "statusCode": "200" + } + }, + "passthroughBehavior": "WHEN_NO_MATCH", + "timeoutInMillis": 29000, + "type": "HTTP", + "uri": "http://petstore-demo-endpoint.execute-api.com/petstore/pets" + }, + "methodResponses": { + "200": { + "statusCode": "200" + } + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "method-response-non-default-authorizer-get": { + "statusCode": "200", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "integration-non-default-authorizer-get": { + "cacheKeyParameters": [], + "cacheNamespace": "", + "connectionType": "INTERNET", + "httpMethod": "GET", + "integrationResponses": { + "200": { + "statusCode": "200" + } + }, + "passthroughBehavior": "WHEN_NO_MATCH", + "timeoutInMillis": 29000, + "type": "HTTP", + "uri": "http://petstore-demo-endpoint.execute-api.com/petstore/pets", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "integration-response-non-default-authorizer-get": { + "statusCode": "200", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "method-pets-get": { + "apiKeyRequired": false, + "authorizationScopes": [ + "email" + ], + "authorizationType": "COGNITO_USER_POOLS", + "authorizerId": "", + "httpMethod": "GET", + "methodIntegration": { + "cacheKeyParameters": [], + "cacheNamespace": "", + "connectionType": "INTERNET", + "httpMethod": "GET", + "passthroughBehavior": "WHEN_NO_MATCH", + "timeoutInMillis": 29000, + "type": "HTTP_PROXY", + "uri": "http://petstore.execute-api.us-west-1.amazonaws.com/petstore/pets" + }, + "methodResponses": { + "200": { + "responseModels": { + "application/json": "Pets" + }, + "responseParameters": { + "method.response.header.Access-Control-Allow-Origin": false + }, + "statusCode": "200" + } + }, + "operationName": "GET HTTP", + "requestParameters": { + "method.request.querystring.page": false, + "method.request.querystring.type": false + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "method-response-pets-get": { + "responseModels": { + "application/json": "Pets" + }, + "responseParameters": { + "method.response.header.Access-Control-Allow-Origin": false + }, + "statusCode": "200", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "integration-pets-get": { + "cacheKeyParameters": [], + "cacheNamespace": "", + "connectionType": "INTERNET", + "httpMethod": "GET", + "passthroughBehavior": "WHEN_NO_MATCH", + "timeoutInMillis": 29000, + "type": "HTTP_PROXY", + "uri": "http://petstore.execute-api.us-west-1.amazonaws.com/petstore/pets", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "integration-response-pets-get": { + "Error": { + "Code": "NotFoundException", + "Message": "Invalid Response status code specified" + }, + "message": "Invalid Response status code specified", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } } } } diff --git a/tests/aws/services/apigateway/test_apigateway_import.validation.json b/tests/aws/services/apigateway/test_apigateway_import.validation.json index 4ab869f05f123..2b90cde06c2ef 100644 --- a/tests/aws/services/apigateway/test_apigateway_import.validation.json +++ b/tests/aws/services/apigateway/test_apigateway_import.validation.json @@ -36,7 +36,7 @@ "last_validated_date": "2024-04-15T21:37:44+00:00" }, "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_cognito_auth_identity_source": { - "last_validated_date": "2024-11-05T11:37:34+00:00" + "last_validated_date": "2024-11-26T21:33:17+00:00" }, "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_global_api_key_authorizer": { "last_validated_date": "2024-04-15T21:36:29+00:00"