From 405bac8ddcdbcd2469730f5964affbe2eacd4806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hermann?= Date: Thu, 12 Sep 2024 11:25:41 -0400 Subject: [PATCH] Configure authentication at Build time --- client/deployment/pom.xml | 16 +- .../deployment/AuthProviderBuildItem.java | 22 ++ .../deployment/GeneratorProcessor.java | 284 ++++++++++++++++++ .../templates/libraries/microprofile/api.qute | 7 + .../auth/compositeAuthenticationProvider.qute | 222 ++------------ .../microprofile/auth/headersFactory.qute | 2 +- .../OpenApiSpecProviderTest.java | 93 ++++++ .../authentication/OperationTest.java | 77 +++++ .../OpenApiClientGeneratorWrapperTest.java | 8 +- .../openapi/generator/it/PetStoreTest.java | 6 +- client/integration-tests/pom.xml | 6 +- .../DummyApiKeyAuthenticationProvider.java | 6 +- client/runtime/pom.xml | 9 +- .../openapi/generator/AuthName.java | 21 ++ .../generator/AuthenticationRecorder.java | 83 +++++ ...lassicOidcClientRequestFilterDelegate.java | 56 ++++ .../openapi/generator/OidcClient.java | 38 +++ .../generator/OpenApiGeneratorConfig.java | 2 +- .../openapi/generator/OpenApiSpec.java | 34 +++ ...activeOidcClientRequestFilterDelegate.java | 81 +++++ .../markers/ApiKeyAuthenticationMarker.java | 31 ++ .../markers/BasicAuthenticationMarker.java | 25 ++ .../markers/BearerAuthenticationMarker.java | 27 ++ .../markers/OauthAuthenticationMarker.java | 25 ++ .../generator/markers/OperationMarker.java | 31 ++ .../providers/AbstractAuthProvider.java | 47 +-- ...thenticationPropagationHeadersFactory.java | 4 +- .../ApiKeyAuthenticationProvider.java | 8 +- .../generator/providers/AuthProvider.java | 2 - .../BasicAuthenticationProvider.java | 9 +- .../BearerAuthenticationProvider.java | 8 +- ...a => CompositeAuthenticationProvider.java} | 11 +- .../OAuth2AuthenticationProvider.java | 23 +- .../providers/OperationAuthInfo.java | 13 + ...a => AbstractOpenApiSpecProviderTest.java} | 6 +- ...ava => ApiKeyOpenApiSpecProviderTest.java} | 16 +- ...java => BasicOpenApiSpecProviderTest.java} | 12 +- ...ava => BearerOpenApiSpecProviderTest.java} | 13 +- 38 files changed, 1070 insertions(+), 314 deletions(-) create mode 100644 client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/AuthProviderBuildItem.java create mode 100644 client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/GeneratorProcessor.java create mode 100644 client/deployment/src/test/java/io/quarkiverse/openapi/generator/deployment/authentication/OpenApiSpecProviderTest.java create mode 100644 client/deployment/src/test/java/io/quarkiverse/openapi/generator/deployment/authentication/OperationTest.java create mode 100644 client/runtime/src/main/java/io/quarkiverse/openapi/generator/AuthName.java create mode 100644 client/runtime/src/main/java/io/quarkiverse/openapi/generator/AuthenticationRecorder.java create mode 100644 client/runtime/src/main/java/io/quarkiverse/openapi/generator/ClassicOidcClientRequestFilterDelegate.java create mode 100644 client/runtime/src/main/java/io/quarkiverse/openapi/generator/OidcClient.java create mode 100644 client/runtime/src/main/java/io/quarkiverse/openapi/generator/OpenApiSpec.java create mode 100644 client/runtime/src/main/java/io/quarkiverse/openapi/generator/ReactiveOidcClientRequestFilterDelegate.java create mode 100644 client/runtime/src/main/java/io/quarkiverse/openapi/generator/markers/ApiKeyAuthenticationMarker.java create mode 100644 client/runtime/src/main/java/io/quarkiverse/openapi/generator/markers/BasicAuthenticationMarker.java create mode 100644 client/runtime/src/main/java/io/quarkiverse/openapi/generator/markers/BearerAuthenticationMarker.java create mode 100644 client/runtime/src/main/java/io/quarkiverse/openapi/generator/markers/OauthAuthenticationMarker.java create mode 100644 client/runtime/src/main/java/io/quarkiverse/openapi/generator/markers/OperationMarker.java rename client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/{AbstractCompositeAuthenticationProvider.java => CompositeAuthenticationProvider.java} (86%) rename client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/{AbstractAuthenticationProviderTest.java => AbstractOpenApiSpecProviderTest.java} (93%) rename client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/{ApiKeyAuthenticationProviderTest.java => ApiKeyOpenApiSpecProviderTest.java} (93%) rename client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/{BasicAuthenticationProviderTest.java => BasicOpenApiSpecProviderTest.java} (76%) rename client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/{BearerAuthenticationProviderTest.java => BearerOpenApiSpecProviderTest.java} (89%) diff --git a/client/deployment/pom.xml b/client/deployment/pom.xml index 1000a5afb..fc8e22fa7 100644 --- a/client/deployment/pom.xml +++ b/client/deployment/pom.xml @@ -118,7 +118,21 @@ quarkus-openapi-generator-test-utils test - + + io.quarkus + quarkus-rest-client-oidc-filter + test + + + jakarta.ws.rs + jakarta.ws.rs-api + test + + + org.eclipse.microprofile.rest.client + microprofile-rest-client-api + test + io.quarkus quarkus-junit5-internal diff --git a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/AuthProviderBuildItem.java b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/AuthProviderBuildItem.java new file mode 100644 index 000000000..652953bac --- /dev/null +++ b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/AuthProviderBuildItem.java @@ -0,0 +1,22 @@ +package io.quarkiverse.openapi.generator.deployment; + +import io.quarkus.builder.item.MultiBuildItem; + +public final class AuthProviderBuildItem extends MultiBuildItem { + + final String openApiSpecId; + final String name; + + AuthProviderBuildItem(String openApiSpecId, String name) { + this.openApiSpecId = openApiSpecId; + this.name = name; + } + + public String getOpenApiSpecId() { + return openApiSpecId; + } + + public String getName() { + return name; + } +} diff --git a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/GeneratorProcessor.java b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/GeneratorProcessor.java new file mode 100644 index 000000000..2cc8c3b33 --- /dev/null +++ b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/GeneratorProcessor.java @@ -0,0 +1,284 @@ +package io.quarkiverse.openapi.generator.deployment; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Instance; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.ClassType; +import org.jboss.jandex.DotName; +import org.jboss.jandex.ParameterizedType; + +import io.quarkiverse.openapi.generator.AuthName; +import io.quarkiverse.openapi.generator.AuthenticationRecorder; +import io.quarkiverse.openapi.generator.ClassicOidcClientRequestFilterDelegate; +import io.quarkiverse.openapi.generator.OidcClient; +import io.quarkiverse.openapi.generator.OpenApiGeneratorConfig; +import io.quarkiverse.openapi.generator.OpenApiSpec; +import io.quarkiverse.openapi.generator.ReactiveOidcClientRequestFilterDelegate; +import io.quarkiverse.openapi.generator.markers.ApiKeyAuthenticationMarker; +import io.quarkiverse.openapi.generator.markers.BasicAuthenticationMarker; +import io.quarkiverse.openapi.generator.markers.BearerAuthenticationMarker; +import io.quarkiverse.openapi.generator.markers.OauthAuthenticationMarker; +import io.quarkiverse.openapi.generator.markers.OperationMarker; +import io.quarkiverse.openapi.generator.providers.ApiKeyIn; +import io.quarkiverse.openapi.generator.providers.AuthProvider; +import io.quarkiverse.openapi.generator.providers.CompositeAuthenticationProvider; +import io.quarkiverse.openapi.generator.providers.OAuth2AuthenticationProvider.OidcClientRequestFilterDelegate; +import io.quarkiverse.openapi.generator.providers.OperationAuthInfo; +import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.deployment.SyntheticBeanBuildItem; +import io.quarkus.deployment.Capabilities; +import io.quarkus.deployment.Capability; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.FeatureBuildItem; + +public class GeneratorProcessor { + + private static final String FEATURE = "openapi-generator"; + private static final DotName OAUTH_AUTHENTICATION_MARKER = DotName.createSimple(OauthAuthenticationMarker.class); + private static final DotName BASIC_AUTHENTICATION_MARKER = DotName.createSimple(BasicAuthenticationMarker.class); + private static final DotName BEARER_AUTHENTICATION_MARKER = DotName.createSimple(BearerAuthenticationMarker.class); + private static final DotName API_KEY_AUTHENTICATION_MARKER = DotName.createSimple(ApiKeyAuthenticationMarker.class); + + private static final DotName OPERATION_MARKER = DotName.createSimple(OperationMarker.class); + + @BuildStep + FeatureBuildItem feature() { + return new FeatureBuildItem(FEATURE); + } + + @BuildStep + void additionalBean( + Capabilities capabilities, + BuildProducer producer) { + + if (capabilities.isPresent(Capability.REST_CLIENT_REACTIVE)) { + producer.produce( + AdditionalBeanBuildItem.builder().addBeanClass(ReactiveOidcClientRequestFilterDelegate.class) + .setDefaultScope(DotName.createSimple(Dependent.class)) + .setUnremovable() + .build()); + } else { + producer.produce( + AdditionalBeanBuildItem.builder().addBeanClass(ClassicOidcClientRequestFilterDelegate.class) + .setDefaultScope(DotName.createSimple(Dependent.class)) + .setUnremovable() + .build()); + } + } + + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + void produceCompositeProviders(AuthenticationRecorder recorder, + List authProviders, + BuildProducer beanProducer) { + Map> providersBySpec = authProviders.stream() + .collect(Collectors.groupingBy(AuthProviderBuildItem::getOpenApiSpecId)); + providersBySpec.forEach((openApiSpecId, providers) -> { + beanProducer.produce(SyntheticBeanBuildItem.configure(CompositeAuthenticationProvider.class) + .scope(Dependent.class) + .addQualifier() + .annotation(OpenApiSpec.class) + .addValue("openApiSpecId", openApiSpecId) + .done() + .addInjectionPoint( + ParameterizedType.create(Instance.class, ClassType.create(AuthProvider.class)), + AnnotationInstance.builder(OpenApiSpec.class) + .add("openApiSpecId", openApiSpecId) + .build()) + .createWith(recorder.recordCompositeProvider(openApiSpecId)) + .done()); + + }); + } + + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + void produceOauthAuthentication(CombinedIndexBuildItem beanArchiveBuildItem, + BuildProducer authenticationProviders, + BuildProducer beanProducer, + AuthenticationRecorder recorder) { + Collection authenticationMarkers = beanArchiveBuildItem.getIndex() + .getAnnotationsWithRepeatable(OAUTH_AUTHENTICATION_MARKER, beanArchiveBuildItem.getIndex()); + + Map> operationsBySpec = getOperationsBySpec(beanArchiveBuildItem); + + for (AnnotationInstance authenticationMarker : authenticationMarkers) { + String name = authenticationMarker.value("name").asString(); + String openApiSpecId = authenticationMarker.value("openApiSpecId").asString(); + List operations = getOperations(operationsBySpec, openApiSpecId, name); + authenticationProviders.produce(new AuthProviderBuildItem(openApiSpecId, name)); + beanProducer.produce(SyntheticBeanBuildItem.configure(AuthProvider.class) + .scope(Dependent.class) + .addQualifier() + .annotation(AuthName.class) + .addValue("name", name) + .done() + .addQualifier() + .annotation(OpenApiSpec.class) + .addValue("openApiSpecId", openApiSpecId) + .done() + .addInjectionPoint(ClassType.create(OidcClientRequestFilterDelegate.class), + AnnotationInstance.builder(OidcClient.class) + .add("name", sanitizeAuthName(name)) + .build()) + .createWith(recorder.recordOauthAuthProvider( + sanitizeAuthName(name), + openApiSpecId, + operations)) + .unremovable() + .done()); + } + } + + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + void produceBasicAuthentication(CombinedIndexBuildItem beanArchiveBuildItem, + BuildProducer authenticationProviders, + BuildProducer beanProducer, + AuthenticationRecorder recorder) { + + Collection authenticationMarkers = beanArchiveBuildItem.getIndex() + .getAnnotationsWithRepeatable(BASIC_AUTHENTICATION_MARKER, beanArchiveBuildItem.getIndex()); + + Map> operationsBySpec = getOperationsBySpec(beanArchiveBuildItem); + for (AnnotationInstance authenticationMarker : authenticationMarkers) { + String name = authenticationMarker.value("name").asString(); + String openApiSpecId = authenticationMarker.value("openApiSpecId").asString(); + + List operations = getOperations(operationsBySpec, openApiSpecId, name); + + authenticationProviders.produce(new AuthProviderBuildItem(openApiSpecId, name)); + + beanProducer.produce(SyntheticBeanBuildItem.configure(AuthProvider.class) + .scope(Dependent.class) + .addQualifier() + .annotation(AuthName.class) + .addValue("name", name) + .done() + .addQualifier() + .annotation(OpenApiSpec.class) + .addValue("openApiSpecId", openApiSpecId) + .done() + .createWith(recorder.recordBasicAuthProvider( + sanitizeAuthName(name), + openApiSpecId, + operations)) + .unremovable() + .done()); + } + } + + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + void produceBearerAuthentication(CombinedIndexBuildItem beanArchiveBuildItem, + BuildProducer authenticationProviders, + BuildProducer beanProducer, + AuthenticationRecorder recorder) { + + Collection authenticationMarkers = beanArchiveBuildItem.getIndex() + .getAnnotationsWithRepeatable(BEARER_AUTHENTICATION_MARKER, beanArchiveBuildItem.getIndex()); + + Map> operationsBySpec = getOperationsBySpec(beanArchiveBuildItem); + for (AnnotationInstance authenticationMarker : authenticationMarkers) { + String name = authenticationMarker.value("name").asString(); + String scheme = authenticationMarker.value("scheme").asString(); + String openApiSpecId = authenticationMarker.value("openApiSpecId").asString(); + + List operations = getOperations(operationsBySpec, openApiSpecId, name); + authenticationProviders.produce(new AuthProviderBuildItem(openApiSpecId, name)); + beanProducer.produce(SyntheticBeanBuildItem.configure(AuthProvider.class) + .scope(Dependent.class) + .addQualifier() + .annotation(AuthName.class) + .addValue("name", name) + .done() + .addQualifier() + .annotation(OpenApiSpec.class) + .addValue("openApiSpecId", openApiSpecId) + .done() + .createWith(recorder.recordBearerAuthProvider( + sanitizeAuthName(name), + scheme, + openApiSpecId, + operations)) + .unremovable() + .done()); + + } + } + + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + void produceApiKeyAuthentication(CombinedIndexBuildItem beanArchiveBuildItem, + BuildProducer authenticationProviders, + BuildProducer beanProducer, + AuthenticationRecorder recorder) { + + Collection authenticationMarkers = beanArchiveBuildItem.getIndex() + .getAnnotationsWithRepeatable(API_KEY_AUTHENTICATION_MARKER, beanArchiveBuildItem.getIndex()); + Map> operationsBySpec = getOperationsBySpec(beanArchiveBuildItem); + for (AnnotationInstance authenticationMarker : authenticationMarkers) { + String name = authenticationMarker.value("name").asString(); + String openApiSpecId = authenticationMarker.value("openApiSpecId").asString(); + String apiKeyName = authenticationMarker.value("apiKeyName").asString(); + ApiKeyIn apiKeyIn = ApiKeyIn.valueOf(authenticationMarker.value("apiKeyIn").asEnum()); + + List operations = getOperations(operationsBySpec, openApiSpecId, name); + + authenticationProviders.produce(new AuthProviderBuildItem(openApiSpecId, name)); + + beanProducer.produce(SyntheticBeanBuildItem.configure(AuthProvider.class) + .scope(Dependent.class) + .addQualifier() + .annotation(AuthName.class) + .addValue("name", name) + .done() + .addQualifier() + .annotation(OpenApiSpec.class) + .addValue("openApiSpecId", openApiSpecId) + .done() + .createWith(recorder.recordApiKeyAuthProvider( + sanitizeAuthName(name), + openApiSpecId, + apiKeyIn, + apiKeyName, + operations)) + .unremovable() + .done()); + } + + } + + private static String sanitizeAuthName(String schemeName) { + return OpenApiGeneratorConfig.getSanitizedSecuritySchemeName(schemeName); + } + + private static Map> getOperationsBySpec(CombinedIndexBuildItem beanArchiveBuildItem) { + Map> operationsBySpec = beanArchiveBuildItem.getIndex() + .getAnnotationsWithRepeatable(OPERATION_MARKER, beanArchiveBuildItem.getIndex()).stream() + .collect(Collectors.groupingBy( + marker -> marker.value("openApiSpecId").asString() + "_" + marker.value("name").asString())); + return operationsBySpec; + } + + private static List getOperations(Map> operationsBySpec, + String openApiSpecId, String name) { + return operationsBySpec.getOrDefault(openApiSpecId + "_" + name, List.of()).stream() + .map(op -> OperationAuthInfo.builder() + .withPath(op.value("path").asString()) + .withId(op.value("operationId").asString()) + .withMethod(op.value("method").asString()) + .build()) + .collect(Collectors.toList()); + } +} diff --git a/client/deployment/src/main/resources/templates/libraries/microprofile/api.qute b/client/deployment/src/main/resources/templates/libraries/microprofile/api.qute index 190e18678..3d291e43d 100644 --- a/client/deployment/src/main/resources/templates/libraries/microprofile/api.qute +++ b/client/deployment/src/main/resources/templates/libraries/microprofile/api.qute @@ -33,6 +33,13 @@ public interface {classname} { /** {#include operationJavaDoc.qute op=op/} */ + {#if op.hasAuthMethods} + {#for auth in op.authMethods} + @io.quarkiverse.openapi.generator.markers.OperationMarker(name="{auth.name}", openApiSpecId="{quarkus-generator.openApiSpecId}", operationId="{op.operationId}", method="{op.httpMethod}", path="{contextPath}{commonPath}{op.path.orEmpty}") + {/for} + {#else} + @io.quarkiverse.openapi.generator.markers.OperationMarker(name="{defaultSecurityScheme}", openApiSpecId="{quarkus-generator.openApiSpecId}", operationId="{op.operationId}", method="{op.httpMethod}", path="{contextPath}{commonPath}{op.path.orEmpty}") + {/if} @jakarta.ws.rs.{op.httpMethod} {#if op.subresourceOperation} @jakarta.ws.rs.Path("{op.path}") diff --git a/client/deployment/src/main/resources/templates/libraries/microprofile/auth/compositeAuthenticationProvider.qute b/client/deployment/src/main/resources/templates/libraries/microprofile/auth/compositeAuthenticationProvider.qute index 4732cf783..c1fd07985 100644 --- a/client/deployment/src/main/resources/templates/libraries/microprofile/auth/compositeAuthenticationProvider.qute +++ b/client/deployment/src/main/resources/templates/libraries/microprofile/auth/compositeAuthenticationProvider.qute @@ -1,204 +1,32 @@ package {apiPackage}.auth; @jakarta.annotation.Priority(jakarta.ws.rs.Priorities.AUTHENTICATION) -public class CompositeAuthenticationProvider extends io.quarkiverse.openapi.generator.providers.AbstractCompositeAuthenticationProvider { +{#for auth in oauthMethods.orEmpty} +@io.quarkiverse.openapi.generator.markers.OauthAuthenticationMarker(name="{auth.name}", openApiSpecId="{configKey}") +{/for} +{#for auth in httpBasicMethods.orEmpty} +@io.quarkiverse.openapi.generator.markers.BasicAuthenticationMarker(name="{auth.name}", openApiSpecId="{quarkus-generator.openApiSpecId}") +{/for} +{#for auth in httpBearerMethods.orEmpty} +@io.quarkiverse.openapi.generator.markers.BearerAuthenticationMarker(name="{auth.name}", openApiSpecId="{quarkus-generator.openApiSpecId}", scheme="{auth.scheme}") +{/for} +{#for auth in apiKeyMethods.orEmpty} +{#if auth.isKeyInQuery} +@io.quarkiverse.openapi.generator.markers.ApiKeyAuthenticationMarker(name="{auth.name}", openApiSpecId="{quarkus-generator.openApiSpecId}", apiKeyIn=io.quarkiverse.openapi.generator.providers.ApiKeyIn.query, apiKeyName="{auth.keyParamName}") +{#else if auth.isKeyInHeader} +@io.quarkiverse.openapi.generator.markers.ApiKeyAuthenticationMarker(name="{auth.name}", openApiSpecId="{quarkus-generator.openApiSpecId}", apiKeyIn=io.quarkiverse.openapi.generator.providers.ApiKeyIn.header, apiKeyName="{auth.keyParamName}") +{#else if auth.isKeyInCookie} +@io.quarkiverse.openapi.generator.markers.ApiKeyAuthenticationMarker(name="{auth.name}", openApiSpecId="{quarkus-generator.openApiSpecId}", apiKeyIn=io.quarkiverse.openapi.generator.providers.ApiKeyIn.cookie, apiKeyName="{auth.keyParamName}") +{/if} +{/for} +public class CompositeAuthenticationProvider implements jakarta.ws.rs.client.ClientRequestFilter { @jakarta.inject.Inject - io.quarkiverse.openapi.generator.OpenApiGeneratorConfig generatorConfig; + @io.quarkiverse.openapi.generator.OpenApiSpec(openApiSpecId="{configKey}") + io.quarkiverse.openapi.generator.providers.CompositeAuthenticationProvider compositeProvider; - {#for auth in oauthMethods.orEmpty} - @jakarta.inject.Inject - io.quarkiverse.openapi.generator.providers.OAuth2AuthenticationProvider oAuth2Provider{auth_index}; - - @jakarta.inject.Inject - OidcClientRequestFilterDelegateImpl{auth_index} oidcClientRequestFilterDelegate{auth_index}; - {/for} - - @jakarta.annotation.PostConstruct - public void init() { - {#for auth in oauthMethods.orEmpty} - oAuth2Provider{auth_index}.init(sanitizeAuthName("{auth.name}"), "{configKey}", oidcClientRequestFilterDelegate{auth_index}); - {/for} - - {#for auth in httpBasicMethods.orEmpty} - io.quarkiverse.openapi.generator.providers.BasicAuthenticationProvider basicAuthProvider{auth_index} = new io.quarkiverse.openapi.generator.providers.BasicAuthenticationProvider("{quarkus-generator.openApiSpecId}", sanitizeAuthName("{auth.name}"), generatorConfig); - this.addAuthenticationProvider(basicAuthProvider{auth_index}); - {#for api in apiInfo.apis} - {#for op in api.operations.operation} - {#if op.hasAuthMethods} - {#for authM in op.authMethods} - {#if authM.name == auth.name} - basicAuthProvider{auth_index}.addOperation(io.quarkiverse.openapi.generator.providers.OperationAuthInfo.builder() - .withPath("{api.contextPath}{api.commonPath}{op.path.orEmpty}") - .withId("{op.operationId}") - .withMethod("{op.httpMethod}") - .build()); - {/if} - {/for} - {#else if defaultSecurityScheme == auth.name} - basicAuthProvider{auth_index}.addOperation(io.quarkiverse.openapi.generator.providers.OperationAuthInfo.builder() - .withPath("{api.contextPath}{api.commonPath}{op.path.orEmpty}") - .withId("{op.operationId}") - .withMethod("{op.httpMethod}") - .build()); - {/if} - {/for} - {/for} - {/for} - {#for auth in oauthMethods.orEmpty} - this.addAuthenticationProvider(oAuth2Provider{auth_index}); - {#for api in apiInfo.apis} - {#for op in api.operations.operation} - {#if op.hasAuthMethods} - {#for authM in op.authMethods} - {#if authM.name == auth.name} - oAuth2Provider{auth_index}.addOperation(io.quarkiverse.openapi.generator.providers.OperationAuthInfo.builder() - .withPath("{api.contextPath}{api.commonPath}{op.path.orEmpty}") - .withId("{op.operationId}") - .withMethod("{op.httpMethod}") - .build()); - {/if} - {/for} - {#else if defaultSecurityScheme == auth.name} - oAuth2Provider{auth_index}.addOperation(io.quarkiverse.openapi.generator.providers.OperationAuthInfo.builder() - .withPath("{api.contextPath}{api.commonPath}{op.path.orEmpty}") - .withId("{op.operationId}") - .withMethod("{op.httpMethod}") - .build()); - {/if} - {/for} - {/for} - {/for} - {#for auth in httpBearerMethods.orEmpty} - io.quarkiverse.openapi.generator.providers.BearerAuthenticationProvider bearerProvider{auth_index} = new io.quarkiverse.openapi.generator.providers.BearerAuthenticationProvider("{quarkus-generator.openApiSpecId}", sanitizeAuthName("{auth.name}"), "{auth.scheme}", generatorConfig); - this.addAuthenticationProvider(bearerProvider{auth_index}); - {#for api in apiInfo.apis} - {#for op in api.operations.operation} - {#if op.hasAuthMethods} - {#for authM in op.authMethods} - {#if authM.name == auth.name} - bearerProvider{auth_index}.addOperation(io.quarkiverse.openapi.generator.providers.OperationAuthInfo.builder() - .withPath("{api.contextPath}{api.commonPath}{op.path.orEmpty}") - .withId("{op.operationId}") - .withMethod("{op.httpMethod}") - .build()); - {/if} - {/for} - {#else if defaultSecurityScheme == auth.name} - bearerProvider{auth_index}.addOperation(io.quarkiverse.openapi.generator.providers.OperationAuthInfo.builder() - .withPath("{api.contextPath}{api.commonPath}{op.path.orEmpty}") - .withId("{op.operationId}") - .withMethod("{op.httpMethod}") - .build()); - {/if} - {/for} - {/for} - {/for} - {#for auth in apiKeyMethods.orEmpty} - {#if auth.isKeyInQuery} - io.quarkiverse.openapi.generator.providers.ApiKeyAuthenticationProvider apiKeyQueryProvider{auth_index} = new io.quarkiverse.openapi.generator.providers.ApiKeyAuthenticationProvider("{quarkus-generator.openApiSpecId}", sanitizeAuthName("{auth.name}"), io.quarkiverse.openapi.generator.providers.ApiKeyIn.query, "{auth.keyParamName}", generatorConfig); - this.addAuthenticationProvider(apiKeyQueryProvider{auth_index}); - {#for api in apiInfo.apis} - {#for op in api.operations.operation} - {#if op.hasAuthMethods} - {#for authM in op.authMethods} - {#if authM.name == auth.name} - apiKeyQueryProvider{auth_index}.addOperation(io.quarkiverse.openapi.generator.providers.OperationAuthInfo.builder() - .withPath("{api.contextPath}{api.commonPath}{op.path.orEmpty}") - .withId("{op.operationId}") - .withMethod("{op.httpMethod}") - .build()); - {/if} - {/for} - {#else if defaultSecurityScheme == auth.name} - apiKeyQueryProvider{auth_index}.addOperation(io.quarkiverse.openapi.generator.providers.OperationAuthInfo.builder() - .withPath("{api.contextPath}{api.commonPath}{op.path.orEmpty}") - .withId("{op.operationId}") - .withMethod("{op.httpMethod}") - .build()); - {/if} - {/for} - {/for} - {/if} - {#if auth.isKeyInHeader} - io.quarkiverse.openapi.generator.providers.ApiKeyAuthenticationProvider apiKeyHeaderProvider{auth_index} = new io.quarkiverse.openapi.generator.providers.ApiKeyAuthenticationProvider("{quarkus-generator.openApiSpecId}", sanitizeAuthName("{auth.name}"), io.quarkiverse.openapi.generator.providers.ApiKeyIn.header, "{auth.keyParamName}", generatorConfig); - this.addAuthenticationProvider(apiKeyHeaderProvider{auth_index}); - {#for api in apiInfo.apis} - {#for op in api.operations.operation} - {#if op.hasAuthMethods} - {#for authM in op.authMethods} - {#if authM.name == auth.name} - apiKeyHeaderProvider{auth_index}.addOperation(io.quarkiverse.openapi.generator.providers.OperationAuthInfo.builder() - .withPath("{api.contextPath}{api.commonPath}{op.path.orEmpty}") - .withId("{op.operationId}") - .withMethod("{op.httpMethod}") - .build()); - {/if} - {/for} - {#else if defaultSecurityScheme == auth.name} - apiKeyHeaderProvider{auth_index}.addOperation(io.quarkiverse.openapi.generator.providers.OperationAuthInfo.builder() - .withPath("{api.contextPath}{api.commonPath}{op.path.orEmpty}") - .withId("{op.operationId}") - .withMethod("{op.httpMethod}") - .build()); - {/if} - {/for} - {/for} - {/if} - {#if auth.isKeyInCookie} - io.quarkiverse.openapi.generator.providers.ApiKeyAuthenticationProvider apiKeyCookieProvider{auth_index} = new io.quarkiverse.openapi.generator.providers.ApiKeyAuthenticationProvider("{quarkus-generator.openApiSpecId}", sanitizeAuthName("{auth.name}"), io.quarkiverse.openapi.generator.providers.ApiKeyIn.cookie, "{auth.keyParamName}", generatorConfig); - this.addAuthenticationProvider(apiKeyCookieProvider{auth_index}); - {#for api in apiInfo.apis} - {#for op in api.operations.operation} - {#if op.hasAuthMethods} - {#for authM in op.authMethods} - {#if authM.name == auth.name} - apiKeyCookieProvider{auth_index}.addOperation(io.quarkiverse.openapi.generator.providers.OperationAuthInfo.builder() - .withPath("{api.contextPath}{api.commonPath}{op.path.orEmpty}") - .withId("{op.operationId}") - .withMethod("{op.httpMethod}") - .build()); - {/if} - {/for} - {#else if defaultSecurityScheme == auth.name} - apiKeyCookieProvider{auth_index}.addOperation(io.quarkiverse.openapi.generator.providers.OperationAuthInfo.builder() - .withPath("{api.contextPath}{api.commonPath}{op.path.orEmpty}") - .withId("{op.operationId}") - .withMethod("{op.httpMethod}") - .build()); - {/if} - {/for} - {/for} - {/if} - {/for} - } - - {#for auth in oauthMethods.orEmpty} - @jakarta.enterprise.context.Dependent - {#if is-resteasy-reactive} - static class OidcClientRequestFilterDelegateImpl{auth_index} extends io.quarkus.oidc.client.reactive.filter.OidcClientRequestReactiveFilter implements io.quarkiverse.openapi.generator.providers.OAuth2AuthenticationProvider.OidcClientRequestFilterDelegate { - - private final String clientId = io.quarkiverse.openapi.generator.OpenApiGeneratorConfig.getSanitizedSecuritySchemeName("{auth.name}"); - - @Override - protected java.util.Optional clientId() { - return java.util.Optional.of(clientId); - } - - @Override - public void filter(jakarta.ws.rs.client.ClientRequestContext requestContext) throws java.io.IOException { - filter((org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestContext)requestContext); - } - } - {#else} - static class OidcClientRequestFilterDelegateImpl{auth_index} extends io.quarkus.oidc.client.filter.OidcClientRequestFilter implements io.quarkiverse.openapi.generator.providers.OAuth2AuthenticationProvider.OidcClientRequestFilterDelegate { - private final String clientId = io.quarkiverse.openapi.generator.OpenApiGeneratorConfig.getSanitizedSecuritySchemeName("{auth.name}"); - - @Override - protected java.util.Optional clientId() { - return java.util.Optional.of(clientId); - } - } - {/if} - {/for} + @java.lang.Override + public void filter(jakarta.ws.rs.client.ClientRequestContext context) throws java.io.IOException { + compositeProvider.filter(context); + }; } diff --git a/client/deployment/src/main/resources/templates/libraries/microprofile/auth/headersFactory.qute b/client/deployment/src/main/resources/templates/libraries/microprofile/auth/headersFactory.qute index 3b541c828..0fc3f221c 100644 --- a/client/deployment/src/main/resources/templates/libraries/microprofile/auth/headersFactory.qute +++ b/client/deployment/src/main/resources/templates/libraries/microprofile/auth/headersFactory.qute @@ -3,7 +3,7 @@ package {apiPackage}.auth; public class AuthenticationPropagationHeadersFactory extends io.quarkiverse.openapi.generator.providers.AbstractAuthenticationPropagationHeadersFactory { @jakarta.inject.Inject - public AuthenticationPropagationHeadersFactory(CompositeAuthenticationProvider compositeProvider, io.quarkiverse.openapi.generator.OpenApiGeneratorConfig generatorConfig, io.quarkiverse.openapi.generator.providers.HeadersProvider headersProvider) { + public AuthenticationPropagationHeadersFactory(@io.quarkiverse.openapi.generator.OpenApiSpec(openApiSpecId="{configKey}") io.quarkiverse.openapi.generator.providers.CompositeAuthenticationProvider compositeProvider, io.quarkiverse.openapi.generator.OpenApiGeneratorConfig generatorConfig, io.quarkiverse.openapi.generator.providers.HeadersProvider headersProvider) { super(compositeProvider, generatorConfig, headersProvider); } diff --git a/client/deployment/src/test/java/io/quarkiverse/openapi/generator/deployment/authentication/OpenApiSpecProviderTest.java b/client/deployment/src/test/java/io/quarkiverse/openapi/generator/deployment/authentication/OpenApiSpecProviderTest.java new file mode 100644 index 000000000..cf3e03c4f --- /dev/null +++ b/client/deployment/src/test/java/io/quarkiverse/openapi/generator/deployment/authentication/OpenApiSpecProviderTest.java @@ -0,0 +1,93 @@ +package io.quarkiverse.openapi.generator.deployment.authentication; + +import static io.quarkiverse.openapi.generator.providers.ApiKeyIn.header; +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.annotation.Priority; +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkiverse.openapi.generator.OpenApiSpec; +import io.quarkiverse.openapi.generator.markers.ApiKeyAuthenticationMarker; +import io.quarkiverse.openapi.generator.markers.BasicAuthenticationMarker; +import io.quarkiverse.openapi.generator.markers.OauthAuthenticationMarker; +import io.quarkiverse.openapi.generator.providers.ApiKeyAuthenticationProvider; +import io.quarkiverse.openapi.generator.providers.AuthProvider; +import io.quarkiverse.openapi.generator.providers.BasicAuthenticationProvider; +import io.quarkiverse.openapi.generator.providers.CompositeAuthenticationProvider; +import io.quarkiverse.openapi.generator.providers.OAuth2AuthenticationProvider; +import io.quarkus.test.QuarkusUnitTest; + +public class OpenApiSpecProviderTest { + + @RegisterExtension + static final QuarkusUnitTest unitTest = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClass(LocalAuthenticationProvider.class) + .addAsResource( + new StringAsset(""" + quarkus.oidc-client.oauth_auth.auth-server-url=localhost + quarkus.oidc-client.oauth_auth1.auth-server-url=localhost + quarkus.oidc-client.oauth_auth2.auth-server-url=localhost + """), + "application.properties")); + + @Inject + @OpenApiSpec(openApiSpecId = "spec_1") + CompositeAuthenticationProvider spec1CompositeProvider; + + @Inject + @OpenApiSpec(openApiSpecId = "spec_2") + CompositeAuthenticationProvider spec2CompositeProvider; + + @Inject + @OpenApiSpec(openApiSpecId = "spec_3") + CompositeAuthenticationProvider spec3CompositeProvider; + + @Inject + @OpenApiSpec(openApiSpecId = "spec_multi") + CompositeAuthenticationProvider multiCompositeProvider; + + @Test + public void checkCompositeProvider() { + assertThat(spec1CompositeProvider.getAuthenticationProviders()).hasSize(1); + assertThat(spec2CompositeProvider.getAuthenticationProviders()).hasSize(1); + assertThat(spec3CompositeProvider.getAuthenticationProviders()).hasSize(1); + AuthProvider authProvider = spec1CompositeProvider.getAuthenticationProviders().get(0); + assertThat(authProvider).isInstanceOf(OAuth2AuthenticationProvider.class); + assertThat(authProvider.getName()).isEqualTo("oauth_auth"); + assertThat(((OAuth2AuthenticationProvider) authProvider).getOpenApiSpecId()).isEqualTo("spec_1"); + authProvider = spec2CompositeProvider.getAuthenticationProviders().get(0); + assertThat(authProvider).isInstanceOf(BasicAuthenticationProvider.class); + assertThat(authProvider.getName()).isEqualTo("basic_auth"); + assertThat(((BasicAuthenticationProvider) authProvider).getOpenApiSpecId()).isEqualTo("spec_2"); + authProvider = spec3CompositeProvider.getAuthenticationProviders().get(0); + assertThat(authProvider).isInstanceOf(ApiKeyAuthenticationProvider.class); + assertThat(authProvider.getName()).isEqualTo("api_key"); + assertThat(((ApiKeyAuthenticationProvider) authProvider).getOpenApiSpecId()).isEqualTo("spec_3"); + } + + @Test + public void checkCompositeProviderWithMultipleAuth() { + Assertions.assertEquals(4, multiCompositeProvider.getAuthenticationProviders().size()); + } + + @Priority(jakarta.ws.rs.Priorities.AUTHENTICATION) + @OauthAuthenticationMarker(name = "oauth_auth", openApiSpecId = "spec_1") + @BasicAuthenticationMarker(name = "basic_auth", openApiSpecId = "spec_2") + @ApiKeyAuthenticationMarker(name = "api_key", openApiSpecId = "spec_3", apiKeyIn = header, apiKeyName = "api_key") + @OauthAuthenticationMarker(name = "oauth_auth1", openApiSpecId = "spec_multi") + @OauthAuthenticationMarker(name = "oauth_auth2", openApiSpecId = "spec_multi") + @BasicAuthenticationMarker(name = "basic_auth1", openApiSpecId = "spec_multi") + @BasicAuthenticationMarker(name = "basic_auth2", openApiSpecId = "spec_multi") + public static class LocalAuthenticationProvider { + + } + +} diff --git a/client/deployment/src/test/java/io/quarkiverse/openapi/generator/deployment/authentication/OperationTest.java b/client/deployment/src/test/java/io/quarkiverse/openapi/generator/deployment/authentication/OperationTest.java new file mode 100644 index 000000000..ea2f7e4fa --- /dev/null +++ b/client/deployment/src/test/java/io/quarkiverse/openapi/generator/deployment/authentication/OperationTest.java @@ -0,0 +1,77 @@ +package io.quarkiverse.openapi.generator.deployment.authentication; + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Response; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkiverse.openapi.generator.OpenApiSpec; +import io.quarkiverse.openapi.generator.annotations.GeneratedClass; +import io.quarkiverse.openapi.generator.markers.BasicAuthenticationMarker; +import io.quarkiverse.openapi.generator.markers.OauthAuthenticationMarker; +import io.quarkiverse.openapi.generator.markers.OperationMarker; +import io.quarkiverse.openapi.generator.providers.CompositeAuthenticationProvider; +import io.quarkiverse.openapi.generator.providers.OperationAuthInfo; +import io.quarkus.test.QuarkusUnitTest; + +public class OperationTest { + + @RegisterExtension + static final QuarkusUnitTest unitTest = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClass(PetApi.class) + .addClass(LocalAuthenticationProvider.class) + .addAsResource( + new StringAsset("quarkus.oidc-client.oauth_auth.auth-server-url=localhost\n"), + "application.properties")); + + @Inject + @OpenApiSpec(openApiSpecId = "petstore_json") + CompositeAuthenticationProvider compositeProvider; + @Inject + @OpenApiSpec(openApiSpecId = "other_spec_json") + CompositeAuthenticationProvider otherProvider; + + @Test + public void test() { + assertThat(compositeProvider.getAuthenticationProviders()).hasSize(1); + assertThat(compositeProvider.getAuthenticationProviders().get(0).operationsToFilter()).hasSize(1); + assertThat(otherProvider.getAuthenticationProviders()).hasSize(1); + assertThat(otherProvider.getAuthenticationProviders().get(0).operationsToFilter()).isEmpty(); + OperationAuthInfo operation = compositeProvider.getAuthenticationProviders().get(0).operationsToFilter().get(0); + assertThat(operation.getOperationId()).isEqualTo("addPet"); + assertThat(operation.getHttpMethod()).isEqualTo("POST"); + assertThat(operation.getPath()).isEqualTo("/api/v3/method1"); + + } + + @RegisterRestClient(baseUri = "http://localhost/api/v3", configKey = "petstore_json") + @GeneratedClass(value = "petstore.json", tag = "Pet") + @ApplicationScoped + public interface PetApi { + + @OperationMarker(name = "oauth_auth", openApiSpecId = "petstore_json", operationId = "addPet", method = "POST", path = "/api/v3/method1") + @Path("/method1") + @POST + Response method1(); + + } + + @jakarta.annotation.Priority(jakarta.ws.rs.Priorities.AUTHENTICATION) + @OauthAuthenticationMarker(name = "oauth_auth", openApiSpecId = "petstore_json") + @BasicAuthenticationMarker(name = "basic_auth", openApiSpecId = "other_spec_json") + public static class LocalAuthenticationProvider { + + } + +} diff --git a/client/deployment/src/test/java/io/quarkiverse/openapi/generator/deployment/wrapper/OpenApiClientGeneratorWrapperTest.java b/client/deployment/src/test/java/io/quarkiverse/openapi/generator/deployment/wrapper/OpenApiClientGeneratorWrapperTest.java index 0bd4002ca..aba0d4fda 100644 --- a/client/deployment/src/test/java/io/quarkiverse/openapi/generator/deployment/wrapper/OpenApiClientGeneratorWrapperTest.java +++ b/client/deployment/src/test/java/io/quarkiverse/openapi/generator/deployment/wrapper/OpenApiClientGeneratorWrapperTest.java @@ -98,15 +98,13 @@ void verifyAuthBasicWithMissingSecurityDefinition(String defaultSecurityScheme) assertThat(methodDeclarations).isNotEmpty(); Optional initMethod = methodDeclarations.stream() - .filter(m -> m.getNameAsString().equals("init")) + .filter(m -> m.getNameAsString().equals("filter")) .findAny(); assertThat(initMethod).isPresent(); String fileContent = compilationUnit.toString(); - assertTrue(fileContent.contains("addAuthenticationProvider")); - if (!defaultSecurityScheme.equals("undefined")) { - assertTrue(fileContent.contains("addOperation")); - } + assertTrue(fileContent.contains( + "@io.quarkiverse.openapi.generator.markers.BasicAuthenticationMarker(name = \"basic\", openApiSpecId = \"petstore_openapi_httpbasic_json\")")); } @Test diff --git a/client/integration-tests/generation-tests/src/test/java/io/quarkiverse/openapi/generator/it/PetStoreTest.java b/client/integration-tests/generation-tests/src/test/java/io/quarkiverse/openapi/generator/it/PetStoreTest.java index 8770ab1ec..f0fe1edf1 100644 --- a/client/integration-tests/generation-tests/src/test/java/io/quarkiverse/openapi/generator/it/PetStoreTest.java +++ b/client/integration-tests/generation-tests/src/test/java/io/quarkiverse/openapi/generator/it/PetStoreTest.java @@ -14,11 +14,11 @@ import com.github.tomakehurst.wiremock.WireMockServer; import io.quarkiverse.openapi.generator.testutils.keycloak.KeycloakRealmResourceManager; -import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.common.WithTestResource; import io.quarkus.test.junit.QuarkusTest; -@QuarkusTestResource(WiremockPetStore.class) -@QuarkusTestResource(KeycloakRealmResourceManager.class) +@WithTestResource(WiremockPetStore.class) +@WithTestResource(KeycloakRealmResourceManager.class) @QuarkusTest public class PetStoreTest { diff --git a/client/integration-tests/pom.xml b/client/integration-tests/pom.xml index 873d2d8dd..77b074f42 100644 --- a/client/integration-tests/pom.xml +++ b/client/integration-tests/pom.xml @@ -67,7 +67,7 @@ io.quarkus - quarkus-oidc-client-filter + quarkus-resteasy-client-oidc-filter io.quarkus @@ -98,11 +98,11 @@ io.quarkus - quarkus-rest-client-reactive-jackson + quarkus-rest-client-jackson io.quarkus - quarkus-oidc-client-reactive-filter + quarkus-rest-client-oidc-filter jakarta.validation diff --git a/client/integration-tests/security/src/main/java/io/quarkiverse/openapi/generator/it/security/auth/DummyApiKeyAuthenticationProvider.java b/client/integration-tests/security/src/main/java/io/quarkiverse/openapi/generator/it/security/auth/DummyApiKeyAuthenticationProvider.java index a04f8371e..3e353d93d 100644 --- a/client/integration-tests/security/src/main/java/io/quarkiverse/openapi/generator/it/security/auth/DummyApiKeyAuthenticationProvider.java +++ b/client/integration-tests/security/src/main/java/io/quarkiverse/openapi/generator/it/security/auth/DummyApiKeyAuthenticationProvider.java @@ -1,6 +1,7 @@ package io.quarkiverse.openapi.generator.it.security.auth; import java.io.IOException; +import java.util.List; import jakarta.annotation.PostConstruct; import jakarta.annotation.Priority; @@ -10,6 +11,7 @@ import jakarta.ws.rs.client.ClientRequestFilter; import io.quarkiverse.openapi.generator.OpenApiGeneratorConfig; +import io.quarkiverse.openapi.generator.SpecItemConfig; import io.quarkiverse.openapi.generator.providers.ApiKeyAuthenticationProvider; import io.quarkiverse.openapi.generator.providers.ApiKeyIn; import io.quarkiverse.openapi.generator.providers.AuthProvider; @@ -25,7 +27,9 @@ public class DummyApiKeyAuthenticationProvider implements ClientRequestFilter { @PostConstruct public void init() { authProvider = new ApiKeyAuthenticationProvider("open_weather_custom_security_yaml", "app_id", ApiKeyIn.query, "appid", - generatorConfig); + generatorConfig.getItemConfig("open_weather_custom_security_yaml") + .flatMap(SpecItemConfig::getAuth).flatMap(x -> x.getItemConfig("app_id")).orElse(null), + List.of()); } @Override diff --git a/client/runtime/pom.xml b/client/runtime/pom.xml index 24dd902af..b0b2ab4aa 100644 --- a/client/runtime/pom.xml +++ b/client/runtime/pom.xml @@ -39,8 +39,13 @@ io.quarkus - quarkus-oidc-client-filter - test + quarkus-rest-client-oidc-filter + true + + + io.quarkus + quarkus-resteasy-client-oidc-filter + true org.assertj diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/AuthName.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/AuthName.java new file mode 100644 index 000000000..60b016285 --- /dev/null +++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/AuthName.java @@ -0,0 +1,21 @@ +package io.quarkiverse.openapi.generator; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import jakarta.inject.Qualifier; + +@Qualifier +@Retention(RUNTIME) +@Target({ METHOD, FIELD, PARAMETER, TYPE }) +public @interface AuthName { + + String name(); + +} diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/AuthenticationRecorder.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/AuthenticationRecorder.java new file mode 100644 index 000000000..c94127edc --- /dev/null +++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/AuthenticationRecorder.java @@ -0,0 +1,83 @@ +package io.quarkiverse.openapi.generator; + +import java.util.List; +import java.util.Objects; +import java.util.function.Function; + +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.util.TypeLiteral; + +import io.quarkiverse.openapi.generator.OpenApiSpec.Literal; +import io.quarkiverse.openapi.generator.providers.ApiKeyAuthenticationProvider; +import io.quarkiverse.openapi.generator.providers.ApiKeyIn; +import io.quarkiverse.openapi.generator.providers.AuthProvider; +import io.quarkiverse.openapi.generator.providers.BasicAuthenticationProvider; +import io.quarkiverse.openapi.generator.providers.BearerAuthenticationProvider; +import io.quarkiverse.openapi.generator.providers.CompositeAuthenticationProvider; +import io.quarkiverse.openapi.generator.providers.OAuth2AuthenticationProvider; +import io.quarkiverse.openapi.generator.providers.OAuth2AuthenticationProvider.OidcClientRequestFilterDelegate; +import io.quarkiverse.openapi.generator.providers.OperationAuthInfo; +import io.quarkus.arc.SyntheticCreationalContext; +import io.quarkus.runtime.annotations.Recorder; + +@Recorder +public class AuthenticationRecorder { + + final OpenApiGeneratorConfig generatorConfig; + + public AuthenticationRecorder(OpenApiGeneratorConfig generatorConfig) { + this.generatorConfig = generatorConfig; + } + + public Function, CompositeAuthenticationProvider> recordCompositeProvider( + String openApiSpec) { + return ctx -> { + List providers = ctx.getInjectedReference(new TypeLiteral>() { + }, new Literal(openApiSpec)).stream().toList(); + return new CompositeAuthenticationProvider(providers); + }; + } + + public Function, AuthProvider> recordApiKeyAuthProvider( + String name, + String openApiSpecId, + ApiKeyIn apiKeyIn, + String apiKeyName, + List operations) { + return context -> new ApiKeyAuthenticationProvider(openApiSpecId, name, apiKeyIn, apiKeyName, + getAuthConfig(openApiSpecId, name), + operations); + } + + public Function, AuthProvider> recordBearerAuthProvider( + String name, + String scheme, + String openApiSpecId, + List operations) { + return context -> new BearerAuthenticationProvider(openApiSpecId, name, scheme, getAuthConfig(openApiSpecId, name), + operations); + } + + public Function, AuthProvider> recordBasicAuthProvider( + String name, + String openApiSpecId, + List operations) { + return context -> new BasicAuthenticationProvider(openApiSpecId, name, getAuthConfig(openApiSpecId, name), operations); + } + + public Function, AuthProvider> recordOauthAuthProvider( + String name, + String openApiSpecId, + List operations) { + return context -> new OAuth2AuthenticationProvider(getAuthConfig(openApiSpecId, name), name, openApiSpecId, + context.getInjectedReference(OidcClientRequestFilterDelegate.class, new OidcClient.Literal(name)), operations); + } + + AuthConfig getAuthConfig(String openApiSpecId, String name) { + return Objects.requireNonNull(generatorConfig, "generatorConfig can't be null.") + .getItemConfig(openApiSpecId) + .flatMap(SpecItemConfig::getAuth) + .flatMap(authsConfig -> authsConfig.getItemConfig(name)) + .orElse(null); + } +} diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/ClassicOidcClientRequestFilterDelegate.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/ClassicOidcClientRequestFilterDelegate.java new file mode 100644 index 000000000..9f25d1057 --- /dev/null +++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/ClassicOidcClientRequestFilterDelegate.java @@ -0,0 +1,56 @@ +package io.quarkiverse.openapi.generator; + +import java.io.IOException; + +import jakarta.annotation.Priority; +import jakarta.enterprise.inject.spi.InjectionPoint; +import jakarta.ws.rs.Priorities; +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.client.ClientRequestFilter; + +import org.jboss.logging.Logger; + +import io.quarkiverse.openapi.generator.providers.OAuth2AuthenticationProvider; +import io.quarkus.oidc.client.runtime.AbstractTokensProducer; +import io.quarkus.oidc.client.runtime.DisabledOidcClientException; + +@Priority(Priorities.AUTHENTICATION) +@OidcClient +public class ClassicOidcClientRequestFilterDelegate extends AbstractTokensProducer + implements ClientRequestFilter, OAuth2AuthenticationProvider.OidcClientRequestFilterDelegate { + + private static final Logger LOG = Logger + .getLogger(ClassicOidcClientRequestFilterDelegate.class); + + final String clientId; + + ClassicOidcClientRequestFilterDelegate(InjectionPoint injectionPoint) { + OidcClient annotation = (OidcClient) injectionPoint.getQualifiers().stream() + .filter(x -> x.annotationType().equals(OidcClient.class)) + .findFirst().orElseThrow(); + + this.clientId = OpenApiGeneratorConfig.getSanitizedSecuritySchemeName(annotation.name()); + } + + @Override + protected java.util.Optional clientId() { + return java.util.Optional.of(clientId); + } + + @Override + public void filter(ClientRequestContext requestContext) throws IOException { + try { + String accessToken = this.getAccessToken(); + requestContext.getHeaders().add("Authorization", "Bearer " + accessToken); + } catch (DisabledOidcClientException ex) { + LOG.debug("Client is disabled, acquiring and propagating the token is not necessary"); + } catch (RuntimeException ex) { + LOG.debugf("Access token is not available, cause: %s, aborting the request", ex.getMessage()); + throw ex; + } + } + + private String getAccessToken() { + return this.awaitTokens().getAccessToken(); + } +} diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/OidcClient.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/OidcClient.java new file mode 100644 index 000000000..6861ba1e9 --- /dev/null +++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/OidcClient.java @@ -0,0 +1,38 @@ +package io.quarkiverse.openapi.generator; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import jakarta.enterprise.util.AnnotationLiteral; +import jakarta.enterprise.util.Nonbinding; +import jakarta.inject.Qualifier; + +@Qualifier +@Retention(RUNTIME) +@Target({ METHOD, FIELD, PARAMETER, TYPE }) +public @interface OidcClient { + + String DEFAULT = "io.quarkiverse.openapi.generator.DEFAULT"; + + @Nonbinding + String name() default DEFAULT; + + final class Literal extends AnnotationLiteral implements OidcClient { + public Literal(String name) { + this.name = name; + } + + final String name; + + @Override + public String name() { + return name; + } + } +} diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/OpenApiGeneratorConfig.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/OpenApiGeneratorConfig.java index 6b297f577..88454d1fa 100644 --- a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/OpenApiGeneratorConfig.java +++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/OpenApiGeneratorConfig.java @@ -11,7 +11,7 @@ /** * This class represents the runtime configurations for the openapi-generator extension. */ -@ConfigRoot(name = OpenApiGeneratorConfig.RUNTIME_TIME_CONFIG_PREFIX, phase = ConfigPhase.RUN_TIME) +@ConfigRoot(name = OpenApiGeneratorConfig.RUNTIME_TIME_CONFIG_PREFIX, phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) public class OpenApiGeneratorConfig { public static final String RUNTIME_TIME_CONFIG_PREFIX = "openapi-generator"; diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/OpenApiSpec.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/OpenApiSpec.java new file mode 100644 index 000000000..7c45afe6a --- /dev/null +++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/OpenApiSpec.java @@ -0,0 +1,34 @@ +package io.quarkiverse.openapi.generator; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import jakarta.enterprise.util.AnnotationLiteral; +import jakarta.inject.Qualifier; + +@Qualifier +@Retention(RUNTIME) +@Target({ METHOD, FIELD, PARAMETER, TYPE }) +public @interface OpenApiSpec { + + String openApiSpecId(); + + final class Literal extends AnnotationLiteral implements OpenApiSpec { + public Literal(String openApiSpecId) { + this.openApiSpecId = openApiSpecId; + } + + final String openApiSpecId; + + @Override + public String openApiSpecId() { + return openApiSpecId; + } + } +} diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/ReactiveOidcClientRequestFilterDelegate.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/ReactiveOidcClientRequestFilterDelegate.java new file mode 100644 index 000000000..d4fb3f4df --- /dev/null +++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/ReactiveOidcClientRequestFilterDelegate.java @@ -0,0 +1,81 @@ +package io.quarkiverse.openapi.generator; + +import java.io.IOException; +import java.util.function.Consumer; + +import jakarta.annotation.Priority; +import jakarta.enterprise.inject.spi.InjectionPoint; +import jakarta.ws.rs.Priorities; +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.core.HttpHeaders; + +import org.jboss.logging.Logger; +import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestContext; +import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestFilter; + +import io.quarkiverse.openapi.generator.providers.OAuth2AuthenticationProvider; +import io.quarkus.oidc.client.Tokens; +import io.quarkus.oidc.client.runtime.AbstractTokensProducer; +import io.quarkus.oidc.client.runtime.DisabledOidcClientException; +import io.quarkus.oidc.common.runtime.OidcConstants; + +@Priority(Priorities.AUTHENTICATION) +@OidcClient +public class ReactiveOidcClientRequestFilterDelegate extends AbstractTokensProducer + implements ResteasyReactiveClientRequestFilter, OAuth2AuthenticationProvider.OidcClientRequestFilterDelegate { + + private static final Logger LOG = Logger + .getLogger(ReactiveOidcClientRequestFilterDelegate.class); + private static final String BEARER_SCHEME_WITH_SPACE = OidcConstants.BEARER_SCHEME + " "; + + final String clientId; + + ReactiveOidcClientRequestFilterDelegate(InjectionPoint injectionPoint) { + OidcClient annotation = (OidcClient) injectionPoint.getQualifiers().stream() + .filter(x -> x.annotationType().equals(OidcClient.class)) + .findFirst().orElseThrow(); + this.clientId = OpenApiGeneratorConfig.getSanitizedSecuritySchemeName(annotation.name()); + } + + @Override + protected java.util.Optional clientId() { + return java.util.Optional.of(clientId); + } + + @Override + protected void initTokens() { + if (earlyTokenAcquisition) { + LOG.debug("Token acquisition will be delayed until this filter is executed to avoid blocking an IO thread"); + } + } + + @Override + public void filter(ClientRequestContext requestContext) throws IOException { + filter((ResteasyReactiveClientRequestContext) requestContext); + } + + @Override + public void filter(ResteasyReactiveClientRequestContext requestContext) { + requestContext.suspend(); + + super.getTokens().subscribe().with(new Consumer<>() { + @Override + public void accept(Tokens tokens) { + requestContext.getHeaders().putSingle(HttpHeaders.AUTHORIZATION, + BEARER_SCHEME_WITH_SPACE + tokens.getAccessToken()); + requestContext.resume(); + } + }, new Consumer<>() { + @Override + public void accept(Throwable t) { + if (t instanceof DisabledOidcClientException) { + LOG.debug("Client is disabled, acquiring and propagating the token is not necessary"); + requestContext.resume(); + } else { + LOG.debugf("Access token is not available, cause: %s, aborting the request", t.getMessage()); + requestContext.resume((t instanceof RuntimeException) ? t : new RuntimeException(t)); + } + } + }); + } +} diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/markers/ApiKeyAuthenticationMarker.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/markers/ApiKeyAuthenticationMarker.java new file mode 100644 index 000000000..3880174bc --- /dev/null +++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/markers/ApiKeyAuthenticationMarker.java @@ -0,0 +1,31 @@ +package io.quarkiverse.openapi.generator.markers; + +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import io.quarkiverse.openapi.generator.providers.ApiKeyIn; + +@Target(TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(ApiKeyAuthenticationMarker.AuthenticationMarkers.class) +public @interface ApiKeyAuthenticationMarker { + + String name(); + + String openApiSpecId(); + + ApiKeyIn apiKeyIn(); + + String apiKeyName(); + + @Target(TYPE) + @Retention(RetentionPolicy.RUNTIME) + @interface AuthenticationMarkers { + ApiKeyAuthenticationMarker[] value(); + } + +} diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/markers/BasicAuthenticationMarker.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/markers/BasicAuthenticationMarker.java new file mode 100644 index 000000000..cd5e81082 --- /dev/null +++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/markers/BasicAuthenticationMarker.java @@ -0,0 +1,25 @@ +package io.quarkiverse.openapi.generator.markers; + +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(BasicAuthenticationMarker.AuthenticationMarkers.class) +public @interface BasicAuthenticationMarker { + + String name(); + + String openApiSpecId(); + + @Target(TYPE) + @Retention(RetentionPolicy.RUNTIME) + @interface AuthenticationMarkers { + BasicAuthenticationMarker[] value(); + } + +} diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/markers/BearerAuthenticationMarker.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/markers/BearerAuthenticationMarker.java new file mode 100644 index 000000000..622540e1b --- /dev/null +++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/markers/BearerAuthenticationMarker.java @@ -0,0 +1,27 @@ +package io.quarkiverse.openapi.generator.markers; + +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(BearerAuthenticationMarker.AuthenticationMarkers.class) +public @interface BearerAuthenticationMarker { + + String name(); + + String openApiSpecId(); + + String scheme(); + + @Target(TYPE) + @Retention(RetentionPolicy.RUNTIME) + @interface AuthenticationMarkers { + BearerAuthenticationMarker[] value(); + } + +} diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/markers/OauthAuthenticationMarker.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/markers/OauthAuthenticationMarker.java new file mode 100644 index 000000000..3d212333d --- /dev/null +++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/markers/OauthAuthenticationMarker.java @@ -0,0 +1,25 @@ +package io.quarkiverse.openapi.generator.markers; + +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(OauthAuthenticationMarker.AuthenticationMarkers.class) +public @interface OauthAuthenticationMarker { + + String name(); + + String openApiSpecId(); + + @Target(TYPE) + @Retention(RetentionPolicy.RUNTIME) + @interface AuthenticationMarkers { + OauthAuthenticationMarker[] value(); + } + +} diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/markers/OperationMarker.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/markers/OperationMarker.java new file mode 100644 index 000000000..1caa67230 --- /dev/null +++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/markers/OperationMarker.java @@ -0,0 +1,31 @@ +package io.quarkiverse.openapi.generator.markers; + +import static java.lang.annotation.ElementType.METHOD; + +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(OperationMarker.OperationMarkers.class) +public @interface OperationMarker { + + String name(); + + String openApiSpecId(); + + String operationId(); + + String path(); + + String method(); + + @Target(METHOD) + @Retention(RetentionPolicy.RUNTIME) + @interface OperationMarkers { + OperationMarker[] value(); + } + +} diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AbstractAuthProvider.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AbstractAuthProvider.java index 5cea1954b..1ede697f4 100644 --- a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AbstractAuthProvider.java +++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AbstractAuthProvider.java @@ -6,15 +6,11 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.Optional; import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MultivaluedMap; import io.quarkiverse.openapi.generator.AuthConfig; -import io.quarkiverse.openapi.generator.AuthsConfig; -import io.quarkiverse.openapi.generator.OpenApiGeneratorConfig; -import io.quarkiverse.openapi.generator.SpecItemConfig; public abstract class AbstractAuthProvider implements AuthProvider { @@ -22,36 +18,17 @@ public abstract class AbstractAuthProvider implements AuthProvider { private static final String CANONICAL_AUTH_CONFIG_PROPERTY_NAME = "quarkus." + RUNTIME_TIME_CONFIG_PREFIX + ".%s.auth.%s.%s"; - private String openApiSpecId; - private String name; - private final OpenApiGeneratorConfig generatorConfig; - private AuthConfig authConfig; + private final String openApiSpecId; + private final String name; + private final AuthConfig authConfig; private final List applyToOperations = new ArrayList<>(); - protected AbstractAuthProvider() { - // Required by CDI. Not supposed to be used. - name = null; - generatorConfig = null; - } - - protected AbstractAuthProvider(OpenApiGeneratorConfig generatorConfig) { - this.generatorConfig = generatorConfig; - } - - protected void init(String name, String openApiSpecId) { + protected AbstractAuthProvider(AuthConfig authConfig, String name, String openApiSpecId, + List operations) { this.name = name; - setOpenApiSpecId(openApiSpecId); - } - - private void setOpenApiSpecId(String openApiSpecId) { this.openApiSpecId = openApiSpecId; - Optional specItemConfig = Objects.requireNonNull(generatorConfig, "generatorConfig can't be null.") - .getItemConfig(openApiSpecId); - if (specItemConfig.isPresent()) { - Optional authsConfig = specItemConfig.get().getAuth(); - authsConfig.ifPresent( - specItemAuthsConfig -> authConfig = specItemAuthsConfig.getItemConfig(name).orElse(null)); - } + this.authConfig = authConfig; + this.applyToOperations.addAll(operations); } public String getOpenApiSpecId() { @@ -63,10 +40,6 @@ public String getName() { return name; } - public OpenApiGeneratorConfig getGeneratorConfig() { - return generatorConfig; - } - public boolean isTokenPropagation() { return authConfig != null && authConfig.getTokenPropagation().orElse(false); } @@ -89,12 +62,6 @@ public List operationsToFilter() { return applyToOperations; } - @Override - public AuthProvider addOperation(OperationAuthInfo operationAuthInfo) { - this.applyToOperations.add(operationAuthInfo); - return this; - } - public String getAuthConfigParam(String paramName, String defaultValue) { if (authConfig != null) { return authConfig.getConfigParam(paramName).orElse(defaultValue); diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AbstractAuthenticationPropagationHeadersFactory.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AbstractAuthenticationPropagationHeadersFactory.java index e96cff5e2..586e14c72 100644 --- a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AbstractAuthenticationPropagationHeadersFactory.java +++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AbstractAuthenticationPropagationHeadersFactory.java @@ -19,11 +19,11 @@ public abstract class AbstractAuthenticationPropagationHeadersFactory implements private static final String HEADER_NAME_PREFIX_FOR_TOKEN_PROPAGATION = "QCG_%s"; private static final String HEADER_NAME_FOR_TOKEN_PROPAGATION = "QCG_%s_%s_%s"; - protected AbstractCompositeAuthenticationProvider compositeProvider; + protected CompositeAuthenticationProvider compositeProvider; protected OpenApiGeneratorConfig generatorConfig; protected HeadersProvider headersProvider; - protected AbstractAuthenticationPropagationHeadersFactory(AbstractCompositeAuthenticationProvider compositeProvider, + protected AbstractAuthenticationPropagationHeadersFactory(CompositeAuthenticationProvider compositeProvider, OpenApiGeneratorConfig generatorConfig, HeadersProvider headersProvider) { this.compositeProvider = compositeProvider; diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/ApiKeyAuthenticationProvider.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/ApiKeyAuthenticationProvider.java index e949fb3ff..03a55f6e5 100644 --- a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/ApiKeyAuthenticationProvider.java +++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/ApiKeyAuthenticationProvider.java @@ -3,6 +3,7 @@ import static io.quarkiverse.openapi.generator.AuthConfig.TOKEN_PROPAGATION; import java.io.IOException; +import java.util.List; import jakarta.ws.rs.client.ClientRequestContext; import jakarta.ws.rs.core.Cookie; @@ -12,7 +13,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.quarkiverse.openapi.generator.OpenApiGeneratorConfig; +import io.quarkiverse.openapi.generator.AuthConfig; import io.quarkiverse.openapi.generator.OpenApiGeneratorException; /** @@ -30,9 +31,8 @@ public class ApiKeyAuthenticationProvider extends AbstractAuthProvider { public ApiKeyAuthenticationProvider(final String openApiSpecId, final String name, final ApiKeyIn apiKeyIn, final String apiKeyName, - final OpenApiGeneratorConfig generatorConfig) { - super(generatorConfig); - init(name, openApiSpecId); + final AuthConfig authConfig, List operations) { + super(authConfig, name, openApiSpecId, operations); this.apiKeyIn = apiKeyIn; this.apiKeyName = apiKeyName; validateConfig(); diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AuthProvider.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AuthProvider.java index f3b00ca6c..fe00447ec 100644 --- a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AuthProvider.java +++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AuthProvider.java @@ -19,6 +19,4 @@ public interface AuthProvider extends ClientRequestFilter { List operationsToFilter(); - AuthProvider addOperation(OperationAuthInfo operationAuthInfo); - } diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/BasicAuthenticationProvider.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/BasicAuthenticationProvider.java index 5f9742e5c..219828b33 100644 --- a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/BasicAuthenticationProvider.java +++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/BasicAuthenticationProvider.java @@ -3,11 +3,12 @@ import static io.quarkiverse.openapi.generator.AuthConfig.TOKEN_PROPAGATION; import java.io.IOException; +import java.util.List; import jakarta.ws.rs.client.ClientRequestContext; import jakarta.ws.rs.core.HttpHeaders; -import io.quarkiverse.openapi.generator.OpenApiGeneratorConfig; +import io.quarkiverse.openapi.generator.AuthConfig; import io.quarkiverse.openapi.generator.OpenApiGeneratorException; /** @@ -20,9 +21,9 @@ public class BasicAuthenticationProvider extends AbstractAuthProvider { static final String USER_NAME = "username"; static final String PASSWORD = "password"; - public BasicAuthenticationProvider(final String openApiSpecId, String name, final OpenApiGeneratorConfig generatorConfig) { - super(generatorConfig); - init(name, openApiSpecId); + public BasicAuthenticationProvider(final String openApiSpecId, String name, final AuthConfig authConfig, + List operations) { + super(authConfig, name, openApiSpecId, operations); validateConfig(); } diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/BearerAuthenticationProvider.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/BearerAuthenticationProvider.java index d2145482c..0e80ab2a5 100644 --- a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/BearerAuthenticationProvider.java +++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/BearerAuthenticationProvider.java @@ -1,11 +1,12 @@ package io.quarkiverse.openapi.generator.providers; import java.io.IOException; +import java.util.List; import jakarta.ws.rs.client.ClientRequestContext; import jakarta.ws.rs.core.HttpHeaders; -import io.quarkiverse.openapi.generator.OpenApiGeneratorConfig; +import io.quarkiverse.openapi.generator.AuthConfig; /** * Provides bearer token authentication or any other valid scheme. @@ -19,9 +20,8 @@ public class BearerAuthenticationProvider extends AbstractAuthProvider { private final String scheme; public BearerAuthenticationProvider(final String openApiSpecId, final String name, final String scheme, - final OpenApiGeneratorConfig generatorConfig) { - super(generatorConfig); - init(name, openApiSpecId); + final AuthConfig authConfig, List operations) { + super(authConfig, name, openApiSpecId, operations); this.scheme = scheme; } diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AbstractCompositeAuthenticationProvider.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/CompositeAuthenticationProvider.java similarity index 86% rename from client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AbstractCompositeAuthenticationProvider.java rename to client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/CompositeAuthenticationProvider.java index a7ce09ff2..3df16cb19 100644 --- a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AbstractCompositeAuthenticationProvider.java +++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/CompositeAuthenticationProvider.java @@ -3,7 +3,6 @@ import static io.quarkiverse.openapi.generator.providers.AbstractAuthenticationPropagationHeadersFactory.propagationHeaderNamePrefix; import java.io.IOException; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -17,12 +16,12 @@ * Composition of supported {@link ClientRequestFilter} defined by a given OpenAPI interface. * This class is used as the base class of generated code. */ -public abstract class AbstractCompositeAuthenticationProvider implements ClientRequestFilter { +public class CompositeAuthenticationProvider implements ClientRequestFilter { - private final List authProviders = new ArrayList<>(); + private final List authProviders; - public final void addAuthenticationProvider(final AuthProvider authProvider) { - this.authProviders.add(authProvider); + public CompositeAuthenticationProvider(List authProviders) { + this.authProviders = List.copyOf(authProviders); } public final List getAuthenticationProviders() { @@ -30,7 +29,7 @@ public final List getAuthenticationProviders() { } @Override - public final void filter(ClientRequestContext requestContext) throws IOException { + public void filter(ClientRequestContext requestContext) throws IOException { Set removableHeaderPrefix = new HashSet<>(); for (AuthProvider authProvider : authProviders) { if (authProvider instanceof AbstractAuthProvider) { diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/OAuth2AuthenticationProvider.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/OAuth2AuthenticationProvider.java index 2a8915b14..ff5368ced 100644 --- a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/OAuth2AuthenticationProvider.java +++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/OAuth2AuthenticationProvider.java @@ -3,6 +3,7 @@ import static io.quarkiverse.openapi.generator.AuthConfig.TOKEN_PROPAGATION; import java.io.IOException; +import java.util.List; import jakarta.ws.rs.client.ClientRequestContext; import jakarta.ws.rs.core.HttpHeaders; @@ -10,31 +11,19 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.quarkiverse.openapi.generator.OpenApiGeneratorConfig; +import io.quarkiverse.openapi.generator.AuthConfig; import io.quarkus.oidc.common.runtime.OidcConstants; -@jakarta.annotation.Priority(jakarta.ws.rs.Priorities.AUTHENTICATION) -@jakarta.enterprise.context.Dependent public class OAuth2AuthenticationProvider extends AbstractAuthProvider { private static final Logger LOGGER = LoggerFactory.getLogger(OAuth2AuthenticationProvider.class); - private OidcClientRequestFilterDelegate delegate; + private final OidcClientRequestFilterDelegate delegate; - @SuppressWarnings("unused") - OAuth2AuthenticationProvider() { - // Required by CDI. Not supposed to be used. - delegate = null; - } - - @jakarta.inject.Inject - public OAuth2AuthenticationProvider(final OpenApiGeneratorConfig generatorConfig) { - super(generatorConfig); - } - - public void init(String name, String openApiSpecId, OidcClientRequestFilterDelegate delegate) { + public OAuth2AuthenticationProvider(final AuthConfig authConfig, String name, + String openApiSpecId, OidcClientRequestFilterDelegate delegate, List operations) { + super(authConfig, name, openApiSpecId, operations); this.delegate = delegate; - super.init(name, openApiSpecId); validateConfig(); } diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/OperationAuthInfo.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/OperationAuthInfo.java index 20e429b18..ec79df461 100644 --- a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/OperationAuthInfo.java +++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/OperationAuthInfo.java @@ -12,6 +12,19 @@ public final class OperationAuthInfo { public OperationAuthInfo() { } + public void setOperationId(final String operationId) { + this.operationId = operationId; + } + + public void setPath(final String path) { + this.pathMatcher = new UrlPatternMatcher(path); + this.path = path; + } + + public void setHttpMethod(final String method) { + this.httpMethod = method; + } + public String getHttpMethod() { return httpMethod; } diff --git a/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/AbstractAuthenticationProviderTest.java b/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/AbstractOpenApiSpecProviderTest.java similarity index 93% rename from client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/AbstractAuthenticationProviderTest.java rename to client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/AbstractOpenApiSpecProviderTest.java index dc5f89583..2387cff63 100644 --- a/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/AbstractAuthenticationProviderTest.java +++ b/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/AbstractOpenApiSpecProviderTest.java @@ -21,7 +21,7 @@ import io.quarkiverse.openapi.generator.SpecItemConfig; @ExtendWith(MockitoExtension.class) -abstract class AbstractAuthenticationProviderTest { +abstract class AbstractOpenApiSpecProviderTest { protected static final String OPEN_API_FILE_SPEC_ID = "open_api_file_spec_id_json"; protected static final String AUTH_SCHEME_NAME = "auth_scheme_name"; @@ -40,11 +40,11 @@ abstract class AbstractAuthenticationProviderTest { +class ApiKeyOpenApiSpecProviderTest extends AbstractOpenApiSpecProviderTest { private static final String API_KEY_NAME = "API_KEY_NAME"; private static final String API_KEY_VALUE = "API_KEY_VALUE"; @@ -42,9 +42,9 @@ protected void createConfiguration() { @Override protected ApiKeyAuthenticationProvider createProvider(String openApiSpecId, String authSchemeName, - OpenApiGeneratorConfig openApiGeneratorConfig) { + AuthConfig authConfig) { return new ApiKeyAuthenticationProvider(openApiSpecId, authSchemeName, ApiKeyIn.header, API_KEY_NAME, - openApiGeneratorConfig); + authConfig, List.of()); } @Test @@ -82,7 +82,7 @@ void filterHeaderCase() throws IOException { void filterQueryCase() throws IOException { doReturn(INVOKED_URI).when(requestContext).getUri(); provider = new ApiKeyAuthenticationProvider(OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME, ApiKeyIn.query, API_KEY_NAME, - generatorConfig); + authConfig, List.of()); provider.filter(requestContext); verify(requestContext).setUri(uriCaptor.capture()); assertThat(uriCaptor.getValue()) @@ -95,7 +95,7 @@ void filterCookieCaseEmpty() throws IOException { final MultivaluedMap headers = new MultivaluedTreeMap<>(); doReturn(headers).when(requestContext).getHeaders(); provider = new ApiKeyAuthenticationProvider(OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME, ApiKeyIn.cookie, API_KEY_NAME, - generatorConfig); + authConfig, List.of()); provider.filter(requestContext); final List cookies = headers.get(HttpHeaders.COOKIE); assertThat(cookies) @@ -114,7 +114,7 @@ void filterCookieCaseExisting() throws IOException { headers.add(HttpHeaders.COOKIE, existingCookie); doReturn(headers).when(requestContext).getHeaders(); provider = new ApiKeyAuthenticationProvider(OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME, ApiKeyIn.cookie, API_KEY_NAME, - generatorConfig); + authConfig, List.of()); provider.filter(requestContext); final List cookies = headers.get(HttpHeaders.COOKIE); assertThat(cookies) @@ -127,7 +127,7 @@ void filterCookieCaseExisting() throws IOException { void tokenPropagationNotSupported() { authConfig.tokenPropagation = Optional.of(true); assertThatThrownBy(() -> new ApiKeyAuthenticationProvider(OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME, ApiKeyIn.header, - API_KEY_NAME, generatorConfig)) + API_KEY_NAME, authConfig, List.of())) .hasMessageContaining("quarkus.openapi-generator.%s.auth.%s.token-propagation", OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME); } diff --git a/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BasicAuthenticationProviderTest.java b/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BasicOpenApiSpecProviderTest.java similarity index 76% rename from client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BasicAuthenticationProviderTest.java rename to client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BasicOpenApiSpecProviderTest.java index 8b388b64c..479f5a71f 100644 --- a/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BasicAuthenticationProviderTest.java +++ b/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BasicOpenApiSpecProviderTest.java @@ -4,15 +4,16 @@ import java.io.IOException; import java.util.Base64; +import java.util.List; import java.util.Optional; import jakarta.ws.rs.core.HttpHeaders; import org.junit.jupiter.api.Test; -import io.quarkiverse.openapi.generator.OpenApiGeneratorConfig; +import io.quarkiverse.openapi.generator.AuthConfig; -class BasicAuthenticationProviderTest extends AbstractAuthenticationProviderTest { +class BasicOpenApiSpecProviderTest extends AbstractOpenApiSpecProviderTest { private static final String USER = "USER"; private static final String PASSWORD = "PASSWORD"; @@ -22,8 +23,8 @@ class BasicAuthenticationProviderTest extends AbstractAuthenticationProviderTest @Override protected BasicAuthenticationProvider createProvider(String openApiSpecId, String authSchemeName, - OpenApiGeneratorConfig openApiGeneratorConfig) { - return new BasicAuthenticationProvider(OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME, openApiGeneratorConfig); + AuthConfig authConfig) { + return new BasicAuthenticationProvider(OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME, authConfig, List.of()); } @Test @@ -37,7 +38,8 @@ void filter() throws IOException { @Test void tokenPropagationNotSupported() { authConfig.tokenPropagation = Optional.of(true); - assertThatThrownBy(() -> new BasicAuthenticationProvider(OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME, generatorConfig)) + assertThatThrownBy( + () -> new BasicAuthenticationProvider(OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME, authConfig, List.of())) .hasMessageContaining("quarkus.openapi-generator.%s.auth.%s.token-propagation", OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME); } diff --git a/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BearerAuthenticationProviderTest.java b/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BearerOpenApiSpecProviderTest.java similarity index 89% rename from client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BearerAuthenticationProviderTest.java rename to client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BearerOpenApiSpecProviderTest.java index bf5b2036f..1e39c96a9 100644 --- a/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BearerAuthenticationProviderTest.java +++ b/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BearerOpenApiSpecProviderTest.java @@ -3,6 +3,7 @@ import static io.quarkiverse.openapi.generator.providers.AbstractAuthenticationPropagationHeadersFactory.propagationHeaderName; import java.io.IOException; +import java.util.List; import java.util.Optional; import java.util.stream.Stream; @@ -13,9 +14,9 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import io.quarkiverse.openapi.generator.OpenApiGeneratorConfig; +import io.quarkiverse.openapi.generator.AuthConfig; -class BearerAuthenticationProviderTest extends AbstractAuthenticationProviderTest { +class BearerOpenApiSpecProviderTest extends AbstractOpenApiSpecProviderTest { private static final String TOKEN = "TOKEN"; private static final String INCOMING_TOKEN = "INCOMING_TOKEN"; @@ -26,8 +27,9 @@ class BearerAuthenticationProviderTest extends AbstractAuthenticationProviderTes @Override protected BearerAuthenticationProvider createProvider(String openApiSpecId, String authSchemeName, - OpenApiGeneratorConfig openApiGeneratorConfig) { - return new BearerAuthenticationProvider(OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME, null, openApiGeneratorConfig); + AuthConfig authConfig) { + return new BearerAuthenticationProvider(OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME, null, authConfig, + List.of()); } @Test @@ -46,7 +48,8 @@ void filterCustomSchemaCase() throws IOException { } private void filter(String bearerScheme, String currentToken, String expectedAuthorizationHeader) throws IOException { - provider = new BearerAuthenticationProvider(OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME, bearerScheme, generatorConfig); + provider = new BearerAuthenticationProvider(OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME, bearerScheme, authConfig, + List.of()); authConfig.authConfigParams.put(BearerAuthenticationProvider.BEARER_TOKEN, currentToken); provider.filter(requestContext); assertHeader(headers, HttpHeaders.AUTHORIZATION, expectedAuthorizationHeader);