Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import java.time.Duration;
import java.time.temporal.TemporalAmount;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -385,4 +386,15 @@ public static TemporalAmount getTemporalOrFallback(final Config configuration, f

return configuration.getTemporal(fallbackPath);
}

/**
* Checks whether a value is present and non-null for at least one of the given paths.
*
* @return {@code true} if value is present and non-null for at least one of the given paths, otherwise {@code false}
* @since 1.10.0
*/
public static boolean hasAnyPath(final Config configuration, final String... pathArray) {

return Arrays.stream(pathArray).anyMatch(configuration::hasPath);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package dev.voidframework.restclient.module;

/**
* All available authentication types.
*
* @since 1.10.0
*/
public enum AuthenticationType {

/**
* API key authentication.
*
* @since 1.10.0
*/
API_KEY,

/**
* Basic authentication.
*
* @since 1.10.0
*/
BASIC,

/**
* Bearer authentication (also called token authentication).
*
* @since 1.10.0
*/
BEARER
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@
import dev.voidframework.restclient.annotation.RestClient;
import dev.voidframework.restclient.exception.RestClientException;
import dev.voidframework.restclient.retrofit.CallAdapterFactory;
import dev.voidframework.restclient.retrofit.interceptor.ApiKeyAuthenticationInterceptor;
import dev.voidframework.restclient.retrofit.interceptor.BasicAuthenticationInterceptor;
import dev.voidframework.restclient.retrofit.interceptor.BearerAuthenticationInterceptor;
import okhttp3.ConnectionPool;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
Expand Down Expand Up @@ -72,7 +76,7 @@ protected void configure() {
"voidframework.restclient.maxIdleConnections"),
keepAliveDurationAsMilliseconds,
TimeUnit.MILLISECONDS);
final OkHttpClient okHttpClient = new OkHttpClient.Builder()
final OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder()
.readTimeout(
ConfigurationUtils.getDurationOrFallback(
this.configuration,
Expand All @@ -83,12 +87,32 @@ protected void configure() {
this.configuration,
restClientConfigurationKeyPrefix + ".connectionTimeout",
"voidframework.restclient.connectionTimeout"))
.connectionPool(connectionPool)
.build();
.connectionPool(connectionPool);

// Authentication interceptor
final boolean needAuthentication = ConfigurationUtils.hasAnyPath(
this.configuration,
restClientConfigurationKeyPrefix + ".authentication.type",
"voidframework.restclient.authentication.type");
if (needAuthentication) {
final AuthenticationType authType = ConfigurationUtils.getEnumOrFallback(
this.configuration,
restClientConfigurationKeyPrefix + ".authentication.type",
AuthenticationType.class,
"voidframework.restclient.authentication.type");

final Interceptor authInterceptor = switch (authType) {
case API_KEY -> createApiKeyAuthenticationInterceptor(restClientConfigurationKeyPrefix);
case BASIC -> createBasicAuthenticationInterceptor(restClientConfigurationKeyPrefix);
case BEARER -> createBearerAuthenticationInterceptor(restClientConfigurationKeyPrefix);
};

okHttpClientBuilder.addInterceptor(authInterceptor);
}

// Creates proxy instance
final Retrofit retrofit = new Retrofit.Builder()
.client(okHttpClient)
.client(okHttpClientBuilder.build())
.addCallAdapterFactory(new CallAdapterFactory())
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(JacksonConverterFactory.create(JsonUtils.objectMapper()))
Expand All @@ -105,6 +129,81 @@ protected void configure() {
}
}

/**
* Create a new {@code ApiKeyAuthenticationInterceptor} instance.
*
* @param restClientConfigurationKeyPrefix The configuration key prefix
* @return Newly created {@code ApiKeyAuthenticationInterceptor} instance
* @see ApiKeyAuthenticationInterceptor
* @since 1.10.0
*/
private Interceptor createApiKeyAuthenticationInterceptor(final String restClientConfigurationKeyPrefix) {

final String apiKeyName = ConfigurationUtils.getStringOrFallback(
this.configuration,
restClientConfigurationKeyPrefix + ".authentication.apiKeyName",
"voidframework.restclient.authentication.apiKeyName");
final String apiKeyValue = ConfigurationUtils.getStringOrFallback(
this.configuration,
restClientConfigurationKeyPrefix + ".authentication.apiKeyValue",
"voidframework.restclient.authentication.apiKeyValue");
final ApiKeyAuthenticationInterceptor.AddTo addTo = ConfigurationUtils.getEnumOrFallback(
this.configuration,
restClientConfigurationKeyPrefix + ".authentication.apiKeyAddTo",
ApiKeyAuthenticationInterceptor.AddTo.class,
"voidframework.restclient.authentication.apiKeyAddTo");

return new ApiKeyAuthenticationInterceptor(apiKeyName, apiKeyValue, addTo);
}

/**
* Create a new {@code BasicAuthenticationInterceptor} instance.
*
* @param restClientConfigurationKeyPrefix The configuration key prefix
* @return Newly created {@code BasicAuthenticationInterceptor} instance
* @see BasicAuthenticationInterceptor
* @since 1.10.0
*/
private Interceptor createBasicAuthenticationInterceptor(final String restClientConfigurationKeyPrefix) {

final String basicUsername = ConfigurationUtils.getStringOrFallback(
this.configuration,
restClientConfigurationKeyPrefix + ".authentication.basicUsername",
"voidframework.restclient.authentication.basicUsername");
final String basicPassword = ConfigurationUtils.getStringOrFallback(
this.configuration,
restClientConfigurationKeyPrefix + ".authentication.basicPassword",
"voidframework.restclient.authentication.basicPassword");
final boolean useIso88591Encoding = ConfigurationUtils.getBooleanOrFallback(
this.configuration,
restClientConfigurationKeyPrefix + ".authentication.basicUseISO88591Encoding",
"voidframework.restclient.authentication.basicUseISO88591Encoding");

return new BasicAuthenticationInterceptor(basicUsername, basicPassword, useIso88591Encoding);
}

/**
* Create a new {@code BearerAuthenticationInterceptor} instance.
*
* @param restClientConfigurationKeyPrefix The configuration key prefix
* @return Newly created {@code BearerAuthenticationInterceptor} instance
* @see BearerAuthenticationInterceptor
* @since 1.10.0
*/
private Interceptor createBearerAuthenticationInterceptor(final String restClientConfigurationKeyPrefix) {

final String bearerPrefix = ConfigurationUtils.getStringOrFallback(
this.configuration,
restClientConfigurationKeyPrefix + ".authentication.bearerPrefix",
"voidframework.restclient.authentication.bearerPrefix");
final String bearerToken = ConfigurationUtils.getStringOrFallback(
this.configuration,
restClientConfigurationKeyPrefix + ".authentication.bearerToken",
"voidframework.restclient.authentication.bearerToken");

return new BearerAuthenticationInterceptor(bearerPrefix, bearerToken);
}

/**
* Retrieves REST Client configuration key prefix.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package dev.voidframework.restclient.retrofit.interceptor;

import dev.voidframework.core.constant.StringConstants;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;

import java.io.IOException;

/**
* Provides API key authentication mechanism.
*
* @since 1.10.0
*/
public final class ApiKeyAuthenticationInterceptor implements Interceptor {

private final String key;
private final String value;
private final AddTo addTo;

/**
* Build a new instance.
*
* @param key The API key variable name
* @param value The API key value
* @param addTo Where to add the API key (ie: HEADERS)
* @since 1.10.0
*/
public ApiKeyAuthenticationInterceptor(final String key,
final String value,
final AddTo addTo) {

this.key = key;
this.value = value;
this.addTo = addTo;
}

@Override
public Response intercept(final Chain chain) throws IOException {

final Request request = chain.request();
final Request authenticatedRequest = switch (this.addTo) {
case COOKIE -> request.newBuilder()
.header("Cookie", this.key + StringConstants.EQUAL + this.value)
.build();
case HEADER -> request.newBuilder()
.header(this.key, this.value)
.build();
case QUERY_PARAMETER -> {
final HttpUrl url = request.url()
.newBuilder()
.addQueryParameter(this.key, this.value)
.build();
yield request.newBuilder().url(url).build();
}
};

return chain.proceed(authenticatedRequest);
}

/**
* Available locations where to add API key.
*
* @since 1.10.0
*/
public enum AddTo {

/**
* API authentication will be added to request cookie.
*
* @since 1.10.0
*/
COOKIE,

/**
* API authentication will be added to request headers.
*
* @since 1.10.0
*/
HEADER,

/**
* API authentication will be added to request query parameters.
*
* @since 1.10.0
*/
QUERY_PARAMETER
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package dev.voidframework.restclient.retrofit.interceptor;

import dev.voidframework.core.constant.StringConstants;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

/**
* Provides Basic authentication mechanism.
*
* @since 1.10.0
*/
public final class BasicAuthenticationInterceptor implements Interceptor {

private final String authorizationHeaderValue;

/**
* Build a new instance.
*
* @param username The username
* @param password The password
* @param useIso88591Encoding Use ISO-8859-1 encoding rather than UTF-8
* @since 1.10.0
*/
public BasicAuthenticationInterceptor(final String username,
final String password,
final boolean useIso88591Encoding) {

final Charset charset = useIso88591Encoding ? StandardCharsets.ISO_8859_1 : StandardCharsets.UTF_8;
final byte[] authData = (username + StringConstants.COLON + password).getBytes(charset);
final String b64Token = Base64.getEncoder().encodeToString(authData);

this.authorizationHeaderValue = "Basic" + StringConstants.SPACE + b64Token;
}

@Override
public Response intercept(final Chain chain) throws IOException {

final Request request = chain.request();
final Request authenticatedRequest = request.newBuilder()
.header("Authorization", authorizationHeaderValue)
.build();

return chain.proceed(authenticatedRequest);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package dev.voidframework.restclient.retrofit.interceptor;

import dev.voidframework.core.constant.StringConstants;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;

import java.io.IOException;

/**
* Provides Bearer authentication (also called token authentication) mechanism.
*
* @since 1.10.0
*/
public final class BearerAuthenticationInterceptor implements Interceptor {

private final String authorizationHeaderValue;

/**
* Build a new instance.
*
* @param bearerPrefix The bearer prefix
* @param bearerToken The bearer token
* @since 1.10.0
*/
public BearerAuthenticationInterceptor(final String bearerPrefix,
final String bearerToken) {

this.authorizationHeaderValue = bearerPrefix + StringConstants.SPACE + bearerToken;
}

@Override
public Response intercept(final Chain chain) throws IOException {

final Request request = chain.request();
final Request authenticatedRequest = request.newBuilder()
.header("Authorization", authorizationHeaderValue)
.build();

return chain.proceed(authenticatedRequest);
}
}
Loading