Skip to content

Commit

Permalink
Issue 70: Upgrade Keycloak 21 (#69)
Browse files Browse the repository at this point in the history
  • Loading branch information
yaol7 authored Aug 23, 2023
1 parent c808f3e commit e44562d
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,28 @@

import io.pravega.common.util.Retry;
import org.apache.http.HttpStatus;
import org.keycloak.OAuth2Constants;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.ClientAuthenticator;
import org.keycloak.authorization.client.Configuration;
import org.keycloak.authorization.client.util.HttpResponseException;
import org.keycloak.common.util.Time;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.protocol.oidc.client.authentication.ClientCredentialsProvider;
import org.keycloak.protocol.oidc.client.authentication.ClientIdAndSecretCredentialsProvider;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
import org.keycloak.representations.idm.authorization.AuthorizationResponse;
import org.keycloak.util.BasicAuthHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.ConnectException;
import java.net.UnknownHostException;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;

/**
Expand Down Expand Up @@ -207,7 +207,7 @@ public static class Builder {
private String audience;
private String configFile;
private String configString;
private BiFunction<Configuration, ClientAuthenticator, AuthzClient> clientSupplier;
private Function<Configuration, AuthzClient> clientSupplier;
private int httpMaxRetries;
private int httpInitialDelayMs;

Expand Down Expand Up @@ -265,7 +265,7 @@ public Builder withCustomRetries(int httpMaxRetries, int httpInitialDelayMs) {
* Sets the Keycloak {@link AuthzClient} authz client provider. For test purposes only.
* @param clientSupplier a function which maps to an authz client.
*/
KeycloakAuthzClient.Builder withAuthzClientSupplier(BiFunction<Configuration, ClientAuthenticator, AuthzClient> clientSupplier) {
KeycloakAuthzClient.Builder withAuthzClientSupplier(Function<Configuration, AuthzClient> clientSupplier) {
this.clientSupplier = clientSupplier;
return this;
}
Expand Down Expand Up @@ -295,9 +295,12 @@ public KeycloakAuthzClient build() {
}

// create the Keycloak authz client
ClientAuthenticator authenticator = createClientAuthenticator(configuration.getResource(), (String) configuration.getCredentials().get("secret"));
ClientCredentialsProvider clientCredentialsProvider = createClientCredentialsProvider(configuration.isPublicClient(),
configuration.getResource(),
(String) configuration.getCredentials().get("secret"));
configuration.setClientCredentialsProvider(clientCredentialsProvider);
AuthzClient client = Retry.withExpBackoff(httpInitialDelayMs, 2, httpMaxRetries)
.retryWhen(isRetryable()).run(() -> clientSupplier.apply(configuration, authenticator));
.retryWhen(isRetryable()).run(() -> clientSupplier.apply(configuration));

// hack: convey the intended audience by setting the configuration resource
configuration.setResource(audience);
Expand All @@ -306,25 +309,28 @@ public KeycloakAuthzClient build() {
}

/**
* Creates a client authenticator which uses HTTP BASIC and client id and secret to authenticate the client.
*
* Note: this implementation captures the client id eagerly, unlike the default client authenticator used by {@link Configuration}.
* This is important since the {@link Configuration} must have the target audience as its resource.
*
* @return the client authenticator
* Creates a ClientCredentialsProvider based on the given parameters.
* @param isPublicClient a boolean indicating if the client is public or not
* @param clientId the client ID
* @param clientSecret the client secret
* @return a ClientCredentialsProvider
* @throws IllegalArgumentException if the client ID or client secret is not provided
*/
private ClientAuthenticator createClientAuthenticator(String clientId, String clientSecret) {
return new ClientAuthenticator() {
/**
* Configures a given Keycloak request to use client credentials for authentication purposes.
* This method is called iff a user access token isn't provided to the builder.
* see: ClientIdAndSecretCredentialsProvider
*/
protected ClientCredentialsProvider createClientCredentialsProvider(boolean isPublicClient, String clientId, String clientSecret) {
return new ClientIdAndSecretCredentialsProvider() {
@Override
public void configureClientCredentials(Map<String, List<String>> requestParams, Map<String, String> requestHeaders) {
public void setClientCredentials(AdapterConfig deployment, Map<String, String> requestHeaders, Map<String, String> formParams) {
Objects.requireNonNull(clientId, "Client ID not provided.");
Objects.requireNonNull(clientSecret, "Client secret not provided.");
requestHeaders.put("Authorization", BasicAuthHelper.createHeader(clientId, clientSecret));

if (isPublicClient) {
LOG.info("public client: add clientId: {} to formParams", clientId);
formParams.put(OAuth2Constants.CLIENT_ID, clientId);
} else {
LOG.info("private client, update basic auth for clientId: {}", clientId);
String authorization = BasicAuthHelper.RFC6749.createHeader(clientId, clientSecret);
requestHeaders.put("Authorization", authorization);
}
}
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.ClientAuthenticator;
import org.keycloak.authorization.client.Configuration;
import org.keycloak.authorization.client.util.HttpResponseException;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.representations.idm.authorization.AuthorizationResponse;
import org.keycloak.util.BasicAuthHelper;
import org.mockito.Mockito;
Expand All @@ -33,10 +33,9 @@
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.BiFunction;
import java.util.function.Function;

import static io.pravega.keycloak.client.KeycloakAuthzClient.DEFAULT_PRAVEGA_CONTROLLER_CLIENT_ID;
import static org.junit.Assert.*;
Expand Down Expand Up @@ -197,13 +196,15 @@ void builderAuthenticator(boolean isFile) {
} else {
KeycloakAuthzClient.builder().withAuthzClientSupplier(supplier).withConfigString(SVC_ACCOUNT_JSON_STRING).build();
}
Map<String, List<String>> requestParams = new HashMap<>();
Map<String, String> formParams = new HashMap<>();
Map<String, String> requestHeaders = new HashMap<>();
supplier.clientAuthenticator.configureClientCredentials(requestParams, requestHeaders);

supplier.configuration.getClientCredentialsProvider().setClientCredentials(mock(AdapterConfig.class), requestHeaders, formParams);
assertTrue(requestHeaders.containsKey("Authorization"));
assertEquals(
requestHeaders.get("Authorization"),
BasicAuthHelper.createHeader("test-client", "b3f202cb-29fe-4d13-afb8-15e787c6e56c"));
assertTrue(formParams.isEmpty());
}

@Test
Expand Down Expand Up @@ -281,15 +282,13 @@ private static String getResourceString(String resourceFilePath) {
}
}

class TestSupplier implements BiFunction<Configuration, ClientAuthenticator, AuthzClient> {
class TestSupplier implements Function<Configuration, AuthzClient> {
Configuration configuration;
ClientAuthenticator clientAuthenticator;
final AuthzClient client = mock(AuthzClient.class);

@Override
public AuthzClient apply(Configuration configuration, ClientAuthenticator clientAuthenticator) {
public AuthzClient apply(Configuration configuration) {
this.configuration = configuration;
this.clientAuthenticator = clientAuthenticator;
return client;
}
}
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

guavaVersion=28.2-jre
junitVersion=4.13.2
keycloakVersion=19.0.3
keycloakVersion=21.1.2
mockitoVersion=3.3.3
pravegaVersion=0.12.0
slf4jVersion=1.7.30
Expand Down

0 comments on commit e44562d

Please sign in to comment.