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 index 1f6978c36..397e43974 100644 --- 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 @@ -34,6 +34,7 @@ import io.quarkiverse.openapi.generator.providers.ApiKeyIn; import io.quarkiverse.openapi.generator.providers.AuthProvider; import io.quarkiverse.openapi.generator.providers.BaseCompositeAuthenticationProvider; +import io.quarkiverse.openapi.generator.providers.CredentialsProvider; import io.quarkiverse.openapi.generator.providers.OperationAuthInfo; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.SyntheticBeanBuildItem; @@ -201,6 +202,7 @@ void produceBasicAuthentication(CombinedIndexBuildItem beanArchiveBuildItem, .annotation(OpenApiSpec.class) .addValue("openApiSpecId", openApiSpecId) .done() + .addInjectionPoint(ClassType.create(DotName.createSimple(CredentialsProvider.class))) .createWith(recorder.recordBasicAuthProvider(sanitizeAuthName(name), openApiSpecId, operations)) .unremovable() .done()); @@ -240,6 +242,7 @@ void produceBearerAuthentication(CombinedIndexBuildItem beanArchiveBuildItem, .annotation(OpenApiSpec.class) .addValue("openApiSpecId", openApiSpecId) .done() + .addInjectionPoint(ClassType.create(DotName.createSimple(CredentialsProvider.class))) .createWith(recorder.recordBearerAuthProvider(sanitizeAuthName(name), scheme, openApiSpecId, operations)) .unremovable() .done()); @@ -282,6 +285,7 @@ void produceApiKeyAuthentication(CombinedIndexBuildItem beanArchiveBuildItem, .annotation(OpenApiSpec.class) .addValue("openApiSpecId", openApiSpecId) .done() + .addInjectionPoint(ClassType.create(DotName.createSimple(CredentialsProvider.class))) .createWith(recorder.recordApiKeyAuthProvider(sanitizeAuthName(name), openApiSpecId, apiKeyIn, apiKeyName, operations)) .unremovable() diff --git a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/template/QuteTemplatingEngineAdapter.java b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/template/QuteTemplatingEngineAdapter.java index 9d3abde30..651ee91b3 100644 --- a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/template/QuteTemplatingEngineAdapter.java +++ b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/template/QuteTemplatingEngineAdapter.java @@ -1,6 +1,5 @@ package io.quarkiverse.openapi.generator.deployment.template; -import java.io.IOException; import java.util.Map; import org.openapitools.codegen.api.AbstractTemplatingEngineAdapter; @@ -12,8 +11,8 @@ public class QuteTemplatingEngineAdapter extends AbstractTemplatingEngineAdapter { - public static final String IDENTIFIER = "qute"; - public static final String[] INCLUDE_TEMPLATES = { + private static final String IDENTIFIER = "qute"; + private static final String[] DEFAULT_TEMPLATES = { "additionalEnumTypeAnnotations.qute", "additionalEnumTypeUnexpectedMember.qute", "additionalModelTypeAnnotations.qute", @@ -60,8 +59,7 @@ public String[] getFileExtensions() { } @Override - public String compileTemplate(TemplatingExecutor executor, Map bundle, String templateFile) - throws IOException { + public String compileTemplate(TemplatingExecutor executor, Map bundle, String templateFile) { this.cacheTemplates(executor); Template template = engine.getTemplate(templateFile); if (template == null) { @@ -72,7 +70,7 @@ public String compileTemplate(TemplatingExecutor executor, Map b } public void cacheTemplates(TemplatingExecutor executor) { - for (String templateId : INCLUDE_TEMPLATES) { + for (String templateId : DEFAULT_TEMPLATES) { Template incTemplate = engine.getTemplate(templateId); if (incTemplate == null) { incTemplate = engine.parse(executor.getFullTemplateContents(templateId)); diff --git a/client/oidc/src/main/java/io/quarkiverse/openapi/generator/oidc/providers/OAuth2AuthenticationProvider.java b/client/oidc/src/main/java/io/quarkiverse/openapi/generator/oidc/providers/OAuth2AuthenticationProvider.java index 211e3bb2a..f4f7009ed 100644 --- a/client/oidc/src/main/java/io/quarkiverse/openapi/generator/oidc/providers/OAuth2AuthenticationProvider.java +++ b/client/oidc/src/main/java/io/quarkiverse/openapi/generator/oidc/providers/OAuth2AuthenticationProvider.java @@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory; import io.quarkiverse.openapi.generator.providers.AbstractAuthProvider; +import io.quarkiverse.openapi.generator.providers.ConfigCredentialsProvider; import io.quarkiverse.openapi.generator.providers.OperationAuthInfo; import io.quarkus.oidc.common.runtime.OidcConstants; @@ -23,7 +24,7 @@ public class OAuth2AuthenticationProvider extends AbstractAuthProvider { public OAuth2AuthenticationProvider(String name, String openApiSpecId, OidcClientRequestFilterDelegate delegate, List operations) { - super(name, openApiSpecId, operations); + super(name, openApiSpecId, operations, new ConfigCredentialsProvider()); this.delegate = delegate; validateConfig(); } 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 index 8672be8e8..74ec86904 100644 --- a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/AuthenticationRecorder.java +++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/AuthenticationRecorder.java @@ -13,6 +13,7 @@ import io.quarkiverse.openapi.generator.providers.BaseCompositeAuthenticationProvider; import io.quarkiverse.openapi.generator.providers.BasicAuthenticationProvider; import io.quarkiverse.openapi.generator.providers.BearerAuthenticationProvider; +import io.quarkiverse.openapi.generator.providers.CredentialsProvider; import io.quarkiverse.openapi.generator.providers.OperationAuthInfo; import io.quarkus.arc.SyntheticCreationalContext; import io.quarkus.runtime.annotations.Recorder; @@ -35,7 +36,8 @@ public Function, AuthProvider> recordAp ApiKeyIn apiKeyIn, String apiKeyName, List operations) { - return context -> new ApiKeyAuthenticationProvider(openApiSpecId, name, apiKeyIn, apiKeyName, operations); + return context -> new ApiKeyAuthenticationProvider(openApiSpecId, name, apiKeyIn, + apiKeyName, operations, context.getInjectedReference(CredentialsProvider.class)); } public Function, AuthProvider> recordBearerAuthProvider( @@ -43,14 +45,18 @@ public Function, AuthProvider> recordBe String scheme, String openApiSpecId, List operations) { - return context -> new BearerAuthenticationProvider(openApiSpecId, name, scheme, operations); + return context -> new BearerAuthenticationProvider(openApiSpecId, name, scheme, + operations, context.getInjectedReference(CredentialsProvider.class)); } public Function, AuthProvider> recordBasicAuthProvider( String name, String openApiSpecId, List operations) { - return context -> new BasicAuthenticationProvider(openApiSpecId, name, operations); + + return context -> new BasicAuthenticationProvider(openApiSpecId, name, + operations, context.getInjectedReference(CredentialsProvider.class)); + } } 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 8d10fae3b..da4983d2e 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 @@ -16,6 +16,8 @@ public abstract class AbstractAuthProvider implements AuthProvider { + CredentialsProvider credentialsProvider; + private static final String BEARER_WITH_SPACE = "Bearer "; private static final String CANONICAL_AUTH_CONFIG_PROPERTY_NAME = "quarkus." + RUNTIME_TIME_CONFIG_PREFIX + ".%s.auth.%s.%s"; @@ -24,10 +26,12 @@ public abstract class AbstractAuthProvider implements AuthProvider { private final String name; private final List applyToOperations = new ArrayList<>(); - protected AbstractAuthProvider(String name, String openApiSpecId, List operations) { + protected AbstractAuthProvider(String name, String openApiSpecId, List operations, + CredentialsProvider credentialsProvider) { this.name = name; this.openApiSpecId = openApiSpecId; this.applyToOperations.addAll(operations); + this.credentialsProvider = credentialsProvider; } protected static String sanitizeBearerToken(String token) { @@ -69,6 +73,10 @@ public List operationsToFilter() { } public final String getCanonicalAuthConfigPropertyName(String authPropertyName) { - return String.format(CANONICAL_AUTH_CONFIG_PROPERTY_NAME, getOpenApiSpecId(), getName(), authPropertyName); + return getCanonicalAuthConfigPropertyName(authPropertyName, getOpenApiSpecId(), getName()); + } + + public static String getCanonicalAuthConfigPropertyName(String authPropertyName, String openApiSpecId, String authName) { + return String.format(CANONICAL_AUTH_CONFIG_PROPERTY_NAME, openApiSpecId, authName, authPropertyName); } } 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 eefe00f56..2f3db8393 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 @@ -11,8 +11,6 @@ import jakarta.ws.rs.core.UriBuilder; import org.eclipse.microprofile.config.ConfigProvider; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import io.quarkiverse.openapi.generator.OpenApiGeneratorException; @@ -21,30 +19,33 @@ */ public class ApiKeyAuthenticationProvider extends AbstractAuthProvider { - private static final Logger LOGGER = LoggerFactory.getLogger(ApiKeyAuthenticationProvider.class); - - static final String API_KEY = "api-key"; static final String USE_AUTHORIZATION_HEADER_VALUE = "use-authorization-header-value"; - private final ApiKeyIn apiKeyIn; private final String apiKeyName; public ApiKeyAuthenticationProvider(final String openApiSpecId, final String name, final ApiKeyIn apiKeyIn, - final String apiKeyName, List operations) { - super(name, openApiSpecId, operations); + final String apiKeyName, List operations, CredentialsProvider credentialsProvider) { + super(name, openApiSpecId, operations, credentialsProvider); this.apiKeyIn = apiKeyIn; this.apiKeyName = apiKeyName; validateConfig(); } + public ApiKeyAuthenticationProvider(final String openApiSpecId, final String name, final ApiKeyIn apiKeyIn, + final String apiKeyName, List operations) { + this(openApiSpecId, name, apiKeyIn, apiKeyName, operations, new ConfigCredentialsProvider()); + } + @Override public void filter(ClientRequestContext requestContext) throws IOException { switch (apiKeyIn) { case query: - requestContext.setUri(UriBuilder.fromUri(requestContext.getUri()).queryParam(apiKeyName, getApiKey()).build()); + requestContext.setUri( + UriBuilder.fromUri(requestContext.getUri()).queryParam(apiKeyName, getApiKey(requestContext)).build()); break; case cookie: - requestContext.getHeaders().add(HttpHeaders.COOKIE, new Cookie.Builder(apiKeyName).value(getApiKey()).build()); + requestContext.getHeaders().add(HttpHeaders.COOKIE, + new Cookie.Builder(apiKeyName).value(getApiKey(requestContext)).build()); break; case header: if (requestContext.getHeaderString("Authorization") != null @@ -52,19 +53,13 @@ public void filter(ClientRequestContext requestContext) throws IOException { && isUseAuthorizationHeaderValue()) { requestContext.getHeaders().putSingle(apiKeyName, requestContext.getHeaderString("Authorization")); } else - requestContext.getHeaders().putSingle(apiKeyName, getApiKey()); + requestContext.getHeaders().putSingle(apiKeyName, getApiKey(requestContext)); break; } } - private String getApiKey() { - final String key = ConfigProvider.getConfig() - .getOptionalValue(getCanonicalAuthConfigPropertyName(API_KEY), String.class).orElse(""); - if (key.isEmpty()) { - LOGGER.warn("configured {} property (see application.properties) is empty. hint: configure it.", - getCanonicalAuthConfigPropertyName(API_KEY)); - } - return key; + private String getApiKey(ClientRequestContext requestContext) { + return credentialsProvider.getApiKey(requestContext, getOpenApiSpecId(), getName()); } private boolean isUseAuthorizationHeaderValue() { 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 6158a1787..e2c8c19b8 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 @@ -8,8 +8,6 @@ import jakarta.ws.rs.client.ClientRequestContext; import jakarta.ws.rs.core.HttpHeaders; -import org.eclipse.microprofile.config.ConfigProvider; - import io.quarkiverse.openapi.generator.OpenApiGeneratorException; /** @@ -19,28 +17,28 @@ */ public class BasicAuthenticationProvider extends AbstractAuthProvider { - static final String USER_NAME = "username"; - static final String PASSWORD = "password"; + public BasicAuthenticationProvider(final String openApiSpecId, String name, List operations, + CredentialsProvider credentialsProvider) { + super(name, openApiSpecId, operations, credentialsProvider); + validateConfig(); + } public BasicAuthenticationProvider(final String openApiSpecId, String name, List operations) { - super(name, openApiSpecId, operations); - validateConfig(); + this(openApiSpecId, name, operations, new ConfigCredentialsProvider()); } - private String getUsername() { - return ConfigProvider.getConfig().getOptionalValue(getCanonicalAuthConfigPropertyName(USER_NAME), String.class) - .orElse(""); + private String getUsername(ClientRequestContext requestContext) { + return credentialsProvider.getBasicUsername(requestContext, getOpenApiSpecId(), getName()); } - private String getPassword() { - return ConfigProvider.getConfig().getOptionalValue(getCanonicalAuthConfigPropertyName(PASSWORD), String.class) - .orElse(""); + private String getPassword(ClientRequestContext requestContext) { + return credentialsProvider.getBasicPassword(requestContext, getOpenApiSpecId(), getName()); } @Override public void filter(ClientRequestContext requestContext) throws IOException { requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, - AuthUtils.basicAuthAccessToken(getUsername(), getPassword())); + AuthUtils.basicAuthAccessToken(getUsername(requestContext), getPassword(requestContext))); } private void 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 da3d1e334..4362e5a2e 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 @@ -6,8 +6,6 @@ import jakarta.ws.rs.client.ClientRequestContext; import jakarta.ws.rs.core.HttpHeaders; -import org.eclipse.microprofile.config.ConfigProvider; - /** * Provides bearer token authentication or any other valid scheme. * @@ -15,16 +13,19 @@ */ public class BearerAuthenticationProvider extends AbstractAuthProvider { - static final String BEARER_TOKEN = "bearer-token"; - private final String scheme; public BearerAuthenticationProvider(final String openApiSpecId, final String name, final String scheme, - List operations) { - super(name, openApiSpecId, operations); + List operations, CredentialsProvider credentialsProvider) { + super(name, openApiSpecId, operations, credentialsProvider); this.scheme = scheme; } + public BearerAuthenticationProvider(final String openApiSpecId, final String name, final String scheme, + List operations) { + this(openApiSpecId, name, scheme, operations, new ConfigCredentialsProvider()); + } + @Override public void filter(ClientRequestContext requestContext) throws IOException { String bearerToken; @@ -32,16 +33,14 @@ public void filter(ClientRequestContext requestContext) throws IOException { bearerToken = getTokenForPropagation(requestContext.getHeaders()); bearerToken = sanitizeBearerToken(bearerToken); } else { - bearerToken = getBearerToken(); + bearerToken = getBearerToken(requestContext); } if (!bearerToken.isBlank()) { - requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, - AuthUtils.authTokenOrBearer(this.scheme, bearerToken)); + requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, AuthUtils.authTokenOrBearer(this.scheme, bearerToken)); } } - private String getBearerToken() { - return ConfigProvider.getConfig().getOptionalValue(getCanonicalAuthConfigPropertyName(BEARER_TOKEN), String.class) - .orElse(""); + private String getBearerToken(ClientRequestContext requestContext) { + return credentialsProvider.getBearerToken(requestContext, getOpenApiSpecId(), getName()); } } diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/ConfigCredentialsProvider.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/ConfigCredentialsProvider.java new file mode 100644 index 000000000..8f78ee1e8 --- /dev/null +++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/ConfigCredentialsProvider.java @@ -0,0 +1,66 @@ +package io.quarkiverse.openapi.generator.providers; + +import jakarta.annotation.Priority; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Alternative; +import jakarta.ws.rs.client.ClientRequestContext; + +import org.eclipse.microprofile.config.ConfigProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Dependent +@Alternative +@Priority(100) +public class ConfigCredentialsProvider implements CredentialsProvider { + + static final String USER_NAME = "username"; + static final String PASSWORD = "password"; + static final String BEARER_TOKEN = "bearer-token"; + static final String API_KEY = "api-key"; + + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigCredentialsProvider.class); + + public ConfigCredentialsProvider() { + + } + + @Override + public String getApiKey(ClientRequestContext requestContext, String openApiSpecId, String authName) { + final String key = ConfigProvider.getConfig() + .getOptionalValue(AbstractAuthProvider.getCanonicalAuthConfigPropertyName(API_KEY, openApiSpecId, authName), + String.class) + .orElse(""); + if (key.isEmpty()) { + LOGGER.warn("configured {} property (see application.properties) is empty. hint: configure it.", + AbstractAuthProvider.getCanonicalAuthConfigPropertyName(API_KEY, openApiSpecId, authName)); + } + return key; + } + + @Override + public String getBasicUsername(ClientRequestContext requestContext, String openApiSpecId, String authName) { + return ConfigProvider.getConfig() + .getOptionalValue(AbstractAuthProvider.getCanonicalAuthConfigPropertyName(USER_NAME, openApiSpecId, authName), + String.class) + .orElse(""); + } + + @Override + public String getBasicPassword(ClientRequestContext requestContext, String openApiSpecId, String authName) { + return ConfigProvider.getConfig() + .getOptionalValue(AbstractAuthProvider.getCanonicalAuthConfigPropertyName(PASSWORD, openApiSpecId, authName), + String.class) + .orElse(""); + } + + @Override + public String getBearerToken(ClientRequestContext requestContext, String openApiSpecId, String authName) { + return ConfigProvider.getConfig() + .getOptionalValue( + AbstractAuthProvider.getCanonicalAuthConfigPropertyName(BEARER_TOKEN, openApiSpecId, authName), + String.class) + .orElse(""); + } + +} diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/CredentialsProvider.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/CredentialsProvider.java new file mode 100644 index 000000000..3f15dd67c --- /dev/null +++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/CredentialsProvider.java @@ -0,0 +1,47 @@ +package io.quarkiverse.openapi.generator.providers; + +import jakarta.ws.rs.client.ClientRequestContext; + +/** + * Provider for security credentials. Clients can implement this interface to control how to provide security credentials in + * runtime. + * Annotate your bean with @RequestScope (or @Dependant) and @Priority(1). + */ +public interface CredentialsProvider { + + /** + * Gets the API Key given the OpenAPI definition and security schema + * + * @param openApiSpecId the OpenAPI Spec identification as defined by the OpenAPI Extension + * @param authName The security schema for this API Key definition + * @return the API Key to use when filtering the request + */ + String getApiKey(ClientRequestContext requestContext, String openApiSpecId, String authName); + + /** + * Gets the username given the OpenAPI definition and security schema + * + * @param openApiSpecId the OpenAPI Spec identification as defined by the OpenAPI Extension + * @param authName The security schema for this Basic Authorization definition + * @return the username to use when filtering the request + */ + String getBasicUsername(ClientRequestContext requestContext, String openApiSpecId, String authName); + + /** + * Gets the password given the OpenAPI definition and security schema + * + * @param openApiSpecId the OpenAPI Spec identification as defined by the OpenAPI Extension + * @param authName The security schema for this Basic Authorization definition + * @return the password to use when filtering the request + */ + String getBasicPassword(ClientRequestContext requestContext, String openApiSpecId, String authName); + + /** + * Gets the Bearer Token given the OpenAPI definition and security schema + * + * @param openApiSpecId the OpenAPI Spec identification as defined by the OpenAPI Extension + * @param authName The security schema for this Bearer Token definition + * @return the Bearer Token to use when filtering the request + */ + String getBearerToken(ClientRequestContext requestContext, String openApiSpecId, String authName); +} diff --git a/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/ApiKeyOpenApiSpecProviderTest.java b/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/ApiKeyOpenApiSpecProviderTest.java index da7b221c4..d6ffd6fab 100644 --- a/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/ApiKeyOpenApiSpecProviderTest.java +++ b/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/ApiKeyOpenApiSpecProviderTest.java @@ -69,7 +69,7 @@ void filterHeaderNotFromAuthorizationHeaderCase() throws IOException { provider.getCanonicalAuthConfigPropertyName(ApiKeyAuthenticationProvider.USE_AUTHORIZATION_HEADER_VALUE), Boolean.class)).thenReturn(Optional.of(false)); when(mockedConfig.getOptionalValue( - provider.getCanonicalAuthConfigPropertyName(ApiKeyAuthenticationProvider.API_KEY), String.class)) + provider.getCanonicalAuthConfigPropertyName(ConfigCredentialsProvider.API_KEY), String.class)) .thenReturn(Optional.of(API_KEY_VALUE)); doReturn(API_KEY_AUTH_HEADER_VALUE).when(requestContext).getHeaderString("Authorization"); diff --git a/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BearerOpenApiSpecProviderTest.java b/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BearerOpenApiSpecProviderTest.java index e1fa92a55..447c39f1c 100644 --- a/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BearerOpenApiSpecProviderTest.java +++ b/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BearerOpenApiSpecProviderTest.java @@ -21,6 +21,14 @@ class BearerOpenApiSpecProviderTest extends AbstractOpenApiSpecProviderTest filterWithPropagationTestValues() { + return Stream.of( + Arguments.of(null, "bearer", "Bearer " + INCOMING_TOKEN), + Arguments.of(null, CUSTOM_SCHEMA, CUSTOM_SCHEMA + " " + INCOMING_TOKEN), + Arguments.of(HEADER_NAME, "bearer", "Bearer " + INCOMING_TOKEN), + Arguments.of(HEADER_NAME, CUSTOM_SCHEMA, CUSTOM_SCHEMA + " " + INCOMING_TOKEN)); + } + @Override protected BearerAuthenticationProvider createProvider() { return new BearerAuthenticationProvider(OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME, null, @@ -65,12 +73,4 @@ void filterWithPropagation(String headerName, headers.putSingle(propagatedHeaderName, INCOMING_TOKEN); filter(bearerScheme, expectedAuthorizationHeader); } - - static Stream filterWithPropagationTestValues() { - return Stream.of( - Arguments.of(null, "bearer", "Bearer " + INCOMING_TOKEN), - Arguments.of(null, CUSTOM_SCHEMA, CUSTOM_SCHEMA + " " + INCOMING_TOKEN), - Arguments.of(HEADER_NAME, "bearer", "Bearer " + INCOMING_TOKEN), - Arguments.of(HEADER_NAME, CUSTOM_SCHEMA, CUSTOM_SCHEMA + " " + INCOMING_TOKEN)); - } } diff --git a/docs/modules/ROOT/pages/client.adoc b/docs/modules/ROOT/pages/client.adoc index e2b6350e8..9745df213 100644 --- a/docs/modules/ROOT/pages/client.adoc +++ b/docs/modules/ROOT/pages/client.adoc @@ -42,6 +42,11 @@ include::./includes/authentication-support.adoc[leveloffset=+1, opts=optional] include::./includes/authorization-token-propagation.adoc[leveloffset=+1, opts=optional] +[[custom-credentials-provider]] +== Custom Credentials Provider + +include::./includes/custom-auth-provider.adoc[leveloffset=+1, opts=optional] + [[circuit-breaker]] == Circuit Breaker diff --git a/docs/modules/ROOT/pages/includes/custom-auth-provider.adoc b/docs/modules/ROOT/pages/includes/custom-auth-provider.adoc new file mode 100644 index 000000000..6d2c5b5a2 --- /dev/null +++ b/docs/modules/ROOT/pages/includes/custom-auth-provider.adoc @@ -0,0 +1,78 @@ + +Instead of relying solely on application properties to provide runtime credentials, you can implement the `io.quarkiverse.openapi.generator.providers.CredentialsProvider` interface. This approach lets you override the default behavior of looking up configuration values and instead supply credentials dynamically at runtime. + +== Default Behavior + +By default, the extension searches for pre-configured values. For instance, to provide a `username` and `password` for a specific OpenAPI security schema definition, you might add these properties to your configuration: + +[source,properties] +---- +quarkus.openapi-generator.myopenapi_yaml.auth.mybasicsecscheme.username=alice +quarkus.openapi-generator.myopenapi_yaml.auth.mybasicsecscheme.password=${SECRET} +---- + +== Overriding with a Custom Implementation + +In some cases, you might need to determine credentials at runtime—for example, when the credentials depend on the server's current URL or other request-specific details. To do this, you can implement the `CredentialsProvider` interface. + +For example: + +[source,java] +---- +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.enterprise.context.RequestScoped; +import jakarta.annotation.Priority; +import jakarta.enterprise.inject.Alternative; +import io.quarkiverse.openapi.generator.providers.CredentialsProvider; + +@RequestScoped +@Alternative +@Priority(10) // A higher priority than the default provider. +public class RuntimeCredentialsProvider implements CredentialsProvider { + + @Override + public String getApiKey(ClientRequestContext requestContext, String openApiSpecId, String authName) { + // Example: return an API key dynamically based on the current request. + // You could inspect requestContext to decide which API key to return. + return "runtimeApiKey"; + } + + @Override + public String getBasicUsername(ClientRequestContext requestContext, String openApiSpecId, String authName) { + // Use requestContext to obtain dynamic request data (like URL or headers) + // for your custom lookup logic. + return "runtimeUser"; + } + + @Override + public String getBasicPassword(ClientRequestContext requestContext, String openApiSpecId, String authName) { + // Return the password dynamically, potentially using details from requestContext. + return "runtimePassword"; + } + + @Override + public String getBearerToken(ClientRequestContext requestContext, String openApiSpecId, String authName) { + // Dynamically compute or look up the bearer token using data in requestContext. + return "runtimeBearerToken"; + } +} +---- + +== How It Works + +* `openApiSpecId` – Identifies the specific OpenAPI specification file. +* `authName` – Refers to the name of the security schema defined in your OpenAPI file. +* `ClientRequestContext` – Provides access to information about the current request (for example, the URL and headers). This data can be crucial if credential resolution depends on runtime request details, such as when different servers or endpoints require different authentication credentials. + +When you implement the provider, the extension will pass the current `ClientRequestContext` along with the identifiers. Your custom code can then use any available request information to dynamically look up or compute the appropriate credentials before they are applied by the authentication filter. + +== Summary + +By implementing the `CredentialsProvider` interface, you gain the following benefits: + +* **Dynamic Credential Resolution:** Obtain credentials at runtime, which is useful if they vary based on the current request context. +* **Custom Lookup Logic:** Use request-specific data (via `ClientRequestContext`) such as URL or headers to determine the correct authentication values. +* **Seamless Integration:** Your custom provider integrates into the authentication filter, replacing static configuration with dynamic behavior as needed. + +This design provides maximum flexibility and control over how authentication credentials are supplied in your application. +