diff --git a/CHANGELOG.md b/CHANGELOG.md
index e19fe6b..7deec56 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,5 +2,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
# Changelog for "keycloak-client"
+## [v1.1.0-SNAPSHOT]
+
+
## [v1.0.1]
- First release (#21389 #22155) provides the basic helper classes for Keycloak tokens retrieve and functions for the gCube framework integration (automatic service discovery).
diff --git a/pom.xml b/pom.xml
index 723fb81..b2fc372 100644
--- a/pom.xml
+++ b/pom.xml
@@ -13,7 +13,7 @@
org.gcube.common
keycloak-client
- 1.0.1
+ 1.1.0-SNAPSHOT
diff --git a/src/main/java/org/gcube/common/keycloak/DefaultKeycloakClient.java b/src/main/java/org/gcube/common/keycloak/DefaultKeycloakClient.java
index 9e6f2d0..1f801aa 100644
--- a/src/main/java/org/gcube/common/keycloak/DefaultKeycloakClient.java
+++ b/src/main/java/org/gcube/common/keycloak/DefaultKeycloakClient.java
@@ -2,6 +2,7 @@ package org.gcube.common.keycloak;
import static org.gcube.resources.discovery.icclient.ICFactory.clientFor;
import static org.gcube.resources.discovery.icclient.ICFactory.queryFor;
+import static org.gcube.common.keycloak.model.OIDCConstants.*;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
@@ -17,6 +18,7 @@ import java.util.stream.Collectors;
import org.gcube.common.gxrest.request.GXHTTPStringRequest;
import org.gcube.common.gxrest.response.inbound.GXInboundResponse;
+import org.gcube.common.keycloak.model.ModelUtils;
import org.gcube.common.keycloak.model.TokenResponse;
import org.gcube.common.resources.gcore.ServiceEndpoint;
import org.gcube.common.resources.gcore.ServiceEndpoint.AccessPoint;
@@ -26,11 +28,6 @@ import org.gcube.resources.discovery.client.queries.api.SimpleQuery;
public class DefaultKeycloakClient implements KeycloakClient {
- private static final String PERMISSION_PARAMETER = "permission";
- private static final String GRANT_TYPE_PARAMETER = "grant_type";
- private static final String UMA_TOKEN_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:uma-ticket";
- private static final String AUDIENCE_PARAMETER = "audience";
-
@Override
public URL findTokenEndpointURL() throws KeycloakClientException {
logger.debug("Checking ScopeProvider's scope presence and format");
@@ -102,41 +99,38 @@ public class DefaultKeycloakClient implements KeycloakClient {
List permissions) throws KeycloakClientException {
if (tokenURL == null) {
- throw new KeycloakClientException("'tokenURL' parameter must be not null");
+ throw new KeycloakClientException("Token URL must be not null");
}
if (authorization == null || "".equals(authorization)) {
- throw new KeycloakClientException("'authorization' parameter must be not null nor empty");
+ throw new KeycloakClientException("Authorization must be not null nor empty");
}
if (audience == null || "".equals(audience)) {
- throw new KeycloakClientException("'audience' parameter must be not null nor empty");
+ throw new KeycloakClientException("Audience must be not null nor empty");
}
logger.debug("Querying token from Keycloak server with URL: {}", tokenURL);
- Map> params = new HashMap<>();
- params.put(GRANT_TYPE_PARAMETER, Arrays.asList(UMA_TOKEN_GRANT_TYPE));
-
- try {
- params.put(AUDIENCE_PARAMETER, Arrays.asList(URLEncoder.encode(checkAudience(audience), "UTF-8")));
- } catch (UnsupportedEncodingException e) {
- logger.error("Cannot URL encode 'audience'", e);
- }
- if (permissions != null && !permissions.isEmpty()) {
- params.put(
- PERMISSION_PARAMETER, permissions.stream().map(s -> {
- try {
- return URLEncoder.encode(s, "UTF-8");
- } catch (UnsupportedEncodingException e) {
- return "";
- }
- }).collect(Collectors.toList()));
- }
-
// Constructing request object
GXHTTPStringRequest request;
try {
+ Map> params = new HashMap<>();
+ params.put(GRANT_TYPE_PARAMETER, Arrays.asList(UMA_TOKEN_GRANT_TYPE));
+
+ params.put(AUDIENCE_PARAMETER, Arrays.asList(URLEncoder.encode(checkAudience(audience), "UTF-8")));
+
+ if (permissions != null && !permissions.isEmpty()) {
+ params.put(
+ PERMISSION_PARAMETER, permissions.stream().map(s -> {
+ try {
+ return URLEncoder.encode(s, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ return "";
+ }
+ }).collect(Collectors.toList()));
+ }
+
String queryString = params.entrySet().stream()
.flatMap(p -> p.getValue().stream().map(v -> p.getKey() + "=" + v))
.reduce((p1, p2) -> p1 + "&" + p2).orElse("");
@@ -168,7 +162,7 @@ public class DefaultKeycloakClient implements KeycloakClient {
} else {
throw KeycloakClientException.create("Unable to get token", response.getHTTPCode(),
response.getHeaderFields()
- .getOrDefault("Content-Type", Collections.singletonList("unknown/unknown")).get(0),
+ .getOrDefault("content-type", Collections.singletonList("unknown/unknown")).get(0),
response.getMessage());
}
}
@@ -185,4 +179,113 @@ public class DefaultKeycloakClient implements KeycloakClient {
return audience;
}
+ @Override
+ public TokenResponse refreshToken(TokenResponse tokenResponse) throws KeycloakClientException {
+ return refreshToken((String) null, tokenResponse);
+ }
+
+ @Override
+ public TokenResponse refreshToken(URL tokenURL, TokenResponse tokenResponse) throws KeycloakClientException {
+ return refreshToken(tokenURL, null, null, tokenResponse);
+ }
+
+ @Override
+ public TokenResponse refreshToken(String clientId, TokenResponse tokenResponse) throws KeycloakClientException {
+ return refreshToken(clientId, null, tokenResponse);
+ }
+
+ @Override
+ public TokenResponse refreshToken(String clientId, String clientSecret, TokenResponse tokenResponse)
+ throws KeycloakClientException {
+
+ return refreshToken(findTokenEndpointURL(), clientId, clientSecret, tokenResponse);
+ }
+
+ @Override
+ public TokenResponse refreshToken(URL tokenURL, String clientId, String clientSecret, TokenResponse tokenResponse)
+ throws KeycloakClientException {
+
+ if (clientId == null) {
+ logger.debug("Client id not set, trying to get it from access token info");
+ try {
+ clientId = ModelUtils.getClientIdFromToken(ModelUtils.getAccessTokenFrom(tokenResponse));
+ } catch (Exception e) {
+ throw new KeycloakClientException("Cannot construct access token object from token response", e);
+ }
+ }
+ return refreshToken(tokenURL, clientId, clientSecret, tokenResponse.getRefreshToken());
+ }
+
+ @Override
+ public TokenResponse refreshToken(String clientId, String refreshTokenJWTString) throws KeycloakClientException {
+ return refreshToken(clientId, null, refreshTokenJWTString);
+ }
+
+ @Override
+ public TokenResponse refreshToken(String clientId, String clientSecret, String refreshTokenJWTString)
+ throws KeycloakClientException {
+
+ return refreshToken(findTokenEndpointURL(), clientId, clientSecret, refreshTokenJWTString);
+ }
+
+ @Override
+ public TokenResponse refreshToken(URL tokenURL, String clientId, String clientSecret, String refreshTokenJWTString)
+ throws KeycloakClientException {
+
+ if (tokenURL == null) {
+ throw new KeycloakClientException("Token URL must be not null");
+ }
+
+ if (clientId == null || "".equals(clientId)) {
+ throw new KeycloakClientException("Client id must be not null nor empty");
+ }
+
+ if (refreshTokenJWTString == null || "".equals(clientId)) {
+ throw new KeycloakClientException("Refresh token JWT encoded string must be not null nor empty");
+ }
+
+ logger.debug("Refreshing token from Keycloak server with URL: {}", tokenURL);
+
+ // Constructing request object
+ GXHTTPStringRequest request;
+ try {
+ Map params = new HashMap<>();
+ params.put(GRANT_TYPE_PARAMETER, REFRESH_TOKEN_GRANT_TYPE);
+ params.put(REFRESH_TOKEN_PARAMETER, refreshTokenJWTString);
+ params.put(CLIENT_ID_PARAMETER, URLEncoder.encode(clientId, "UTF-8"));
+ if (clientSecret != null && !"".equals(clientSecret)) {
+ params.put(CLIENT_SECRET_PARAMETER, URLEncoder.encode(clientSecret, "UTF-8"));
+ }
+ String queryString = params.entrySet().stream()
+ .map(p -> p.getKey() + "=" + p.getValue())
+ .reduce((p1, p2) -> p1 + "&" + p2).orElse("");
+
+ request = GXHTTPStringRequest.newRequest(tokenURL.toString()).header("Content-Type",
+ "application/x-www-form-urlencoded").withBody(queryString);
+
+ request.isExternalCall(true);
+ } catch (Exception e) {
+ throw new KeycloakClientException("Cannot construct the request object correctly", e);
+ }
+
+ GXInboundResponse response;
+ try {
+ response = request.post();
+ } catch (Exception e) {
+ throw new KeycloakClientException("Cannot send request correctly", e);
+ }
+ if (response.isSuccessResponse()) {
+ try {
+ return response.tryConvertStreamedContentFromJson(TokenResponse.class);
+ } catch (Exception e) {
+ throw new KeycloakClientException("Cannot construct token response object correctly", e);
+ }
+ } else {
+ throw KeycloakClientException.create("Unable to get token", response.getHTTPCode(),
+ response.getHeaderFields()
+ .getOrDefault("content-type", Collections.singletonList("unknown/unknown")).get(0),
+ response.getMessage());
+ }
+ }
+
}
diff --git a/src/main/java/org/gcube/common/keycloak/KeycloakClient.java b/src/main/java/org/gcube/common/keycloak/KeycloakClient.java
index d0807ab..a0a10e6 100644
--- a/src/main/java/org/gcube/common/keycloak/KeycloakClient.java
+++ b/src/main/java/org/gcube/common/keycloak/KeycloakClient.java
@@ -18,6 +18,7 @@ public interface KeycloakClient {
/**
* Finds the keycloak endpoint {@link URL} discovering it in the current scope provided by {@link ScopeProvider}
+ *
* @return the keycloak endpoint URL in the current scope
* @throws KeycloakClientException if something goes wrong discovering the endpoint URL
*/
@@ -54,7 +55,7 @@ public interface KeycloakClient {
throws KeycloakClientException;
/**
- * Queries an UMA token from the discovered Keycloak server in the current scope, by using provided clientId and client secret
+ * Queries an UMA token from the Keycloak server discovered in the current scope, by using provided clientId and client secret
* for the given audience (context), in URLEncoded form or not, and optionally a list of permissions.
*
* @param clientId the client id
@@ -68,7 +69,7 @@ public interface KeycloakClient {
throws KeycloakClientException;
/**
- * Queries an UMA token from the discovered Keycloak server in the current scope, by using provided clientId and client secret
+ * Queries an UMA token from the Keycloak server discovered in the current scope, by using provided clientId and client secret
* for the current scope audience (context), in URLEncoded form or not, and optionally a list of permissions.
*
* @param clientId the client id
@@ -79,4 +80,114 @@ public interface KeycloakClient {
*/
TokenResponse queryUMAToken(String clientId, String clientSecret, List permissions)
throws KeycloakClientException;
+
+ /**
+ * Refreshes a previously issued token from the Keycloak server discovered in the current scope using the refresh
+ * token JWT encoded string in the token response object.
+ *
+ * Client id will be read from "issued for" access token's claim and client secret will be not sent.
+ *
NOTE: For public
clients types only.
+ *
+ * @param tokenResponse the previously issued token as {@link TokenResponse} object
+ * @return the refreshed token as {@link TokenResponse} object
+ * @throws KeycloakClientException if something goes wrong performing the refresh query
+ */
+ TokenResponse refreshToken(TokenResponse tokenResponse) throws KeycloakClientException;
+
+ /**
+ * Refreshes a previously issued token from the Keycloak server using the refresh token JWT encoded string in the
+ * token response object.
+ *
+ * Client id will be read from "issued for" access token's claim and client secret will be not sent.
+ *
NOTE: For public
clients types only.
+ *
+ * @param tokenUrl the token endpoint {@link URL} of the OIDC server
+ * @param tokenResponse the previously issued token as {@link TokenResponse} object
+ * @return the refreshed token as {@link TokenResponse} object
+ * @throws KeycloakClientException if something goes wrong performing the refresh query
+ */
+ TokenResponse refreshToken(URL tokenURL, TokenResponse tokenResponse) throws KeycloakClientException;
+
+ /**
+ * Refreshes a previously issued token from the Keycloak server discovered in the current scope using the refresh
+ * token JWT encoded string in the token response object and the provided client id.
+ *
+ * Client secret will be not sent.
+ *
NOTE: For public
clients types only.
+ *
+ * @param clientId the requestor client id, may be null
and in this case will be take from the access token "issued for" claim
+ * @param tokenResponse the previously issued token as {@link TokenResponse} object
+ * @return the refreshed token as {@link TokenResponse} object
+ * @throws KeycloakClientException if something goes wrong performing the refresh query
+ */
+ TokenResponse refreshToken(String clientId, TokenResponse tokenResponse) throws KeycloakClientException;
+
+ /**
+ * Refreshes a previously issued token from the Keycloak server discovered in the current scope using the refresh
+ * token JWT encoded string in the token response object and the provided client id and secret.
+ *
+ * @param clientId the requestor client id, may be null
and in this case will be take from the access token "issued for" claim
+ * @param clientSecret the requestor client secret, may be null
for non-confidential clients
+ * @param tokenResponse the previously issued token as {@link TokenResponse} object
+ * @return the refreshed token as {@link TokenResponse} object
+ * @throws KeycloakClientException if something goes wrong performing the refresh query
+ */
+ TokenResponse refreshToken(String clientId, String clientSecret, TokenResponse tokenResponse)
+ throws KeycloakClientException;
+
+ /**
+ * Refreshes a previously issued token from the Keycloak server using the refresh token JWT encoded string in the
+ * token response object and the provided client id and secret.
+ *
+ * @param clientId the requestor client id, may be null
and in this case will be take from the access token "issued for" claim
+ * @param clientSecret the requestor client secret, may be null
for non-confidential clients
+ * @param tokenResponse the previously issued token as {@link TokenResponse} object
+ * @return the refreshed token as {@link TokenResponse} object
+ * @throws KeycloakClientException if something goes wrong performing the refresh query
+ */
+ TokenResponse refreshToken(URL tokenURL, String clientId, String clientSecret, TokenResponse tokenResponse)
+ throws KeycloakClientException;
+
+ /**
+ * Refreshes a previously issued token from the Keycloak server discovered in the current scope using the provided
+ * client id and the refresh token JWT encoded string obtained with the access token in the previous token response.
+ *
+ * Client secret will be not used.
+ *
NOTE: For public
clients types only.
+ *
+ * @param clientId the requestor client id
+ * @param refreshTokenJWTString the previously issued refresh token JWT string taken from the same token response of the access token parameter
+ * @return the refreshed token as {@link TokenResponse} object
+ * @throws KeycloakClientException if something goes wrong performing the refresh query
+ */
+ TokenResponse refreshToken(String clientId, String refreshTokenJWTString) throws KeycloakClientException;
+
+ /**
+ * Refreshes a previously issued token from the Keycloak server discovered in the current scope using the provided
+ * client id and secret and the refresh token JWT encoded string obtained with the access token in the previous
+ * token response.
+ *
+ * @param clientId the requestor client id
+ * @param clientSecret the requestor client secret, may be null
for non-confidential clients
+ * @param refreshTokenJWTString the previously issued refresh token JWT string taken from the same token response of the access token parameter
+ * @return the refreshed token as {@link TokenResponse} object
+ * @throws KeycloakClientException if something goes wrong performing the refresh query
+ */
+ TokenResponse refreshToken(String clientId, String clientSecret, String refreshTokenJWTString)
+ throws KeycloakClientException;
+
+ /**
+ * Refreshes a previously issued token from the Keycloak server by using the client id and secret
+ * and the refresh token JWT encoded string obtained with the access token in the previous token response.
+ *
+ * @param tokenUrl the token endpoint {@link URL} of the OIDC server
+ * @param clientId the requestor client id
+ * @param clientSecret the requestor client secret, may be null
for non-confidential clients
+ * @param refreshTokenJWTString the previously issued refresh token JWT string
+ * @return the refreshed token as {@link TokenResponse} object
+ * @throws KeycloakClientException if something goes wrong performing the refresh query
+ */
+ TokenResponse refreshToken(URL tokenURL, String clientId, String clientSecret, String refreshTokenJWTString)
+ throws KeycloakClientException;
+
}
\ No newline at end of file
diff --git a/src/main/java/org/gcube/common/keycloak/KeycloakClientException.java b/src/main/java/org/gcube/common/keycloak/KeycloakClientException.java
index dd1ea1e..1222769 100644
--- a/src/main/java/org/gcube/common/keycloak/KeycloakClientException.java
+++ b/src/main/java/org/gcube/common/keycloak/KeycloakClientException.java
@@ -35,7 +35,7 @@ public class KeycloakClientException extends Exception {
super(message);
}
- public KeycloakClientException(String message, Exception cause) {
+ public KeycloakClientException(String message, Throwable cause) {
super(message, cause);
}
diff --git a/src/main/java/org/gcube/common/keycloak/model/ModelUtils.java b/src/main/java/org/gcube/common/keycloak/model/ModelUtils.java
index c8fabb0..a4225a5 100644
--- a/src/main/java/org/gcube/common/keycloak/model/ModelUtils.java
+++ b/src/main/java/org/gcube/common/keycloak/model/ModelUtils.java
@@ -1,6 +1,8 @@
package org.gcube.common.keycloak.model;
+import java.util.Arrays;
import java.util.Base64;
+import java.util.List;
import org.gcube.com.fasterxml.jackson.annotation.JsonInclude.Include;
import org.gcube.com.fasterxml.jackson.core.JsonProcessingException;
@@ -13,6 +15,8 @@ public class ModelUtils {
protected static final Logger logger = LoggerFactory.getLogger(ModelUtils.class);
+ private static final String ACCOUNT_AUDIENCE_RESOURCE = "account";
+
private static final ObjectMapper mapper = new ObjectMapper();
static {
@@ -33,31 +37,40 @@ public class ModelUtils {
}
}
- private static byte[] getDecodedPayload(String value) {
- return getBase64Decoded(getEncodedPayload(value));
+ public static String getAccessTokenPayloadJSONStringFrom(TokenResponse tokenResponse) throws Exception {
+ return getAccessTokenPayloadJSONStringFrom(tokenResponse, true);
}
- public static String getAccessTokenPayloadStringFrom(TokenResponse tokenResponse) throws Exception {
- return getAccessTokenPayloadStringFrom(tokenResponse, true);
- }
-
- public static String getAccessTokenPayloadStringFrom(TokenResponse tokenResponse, boolean prettyPrint) throws Exception {
+ public static String getAccessTokenPayloadJSONStringFrom(TokenResponse tokenResponse, boolean prettyPrint)
+ throws Exception {
return toJSONString(getAccessTokenFrom(tokenResponse, Object.class), prettyPrint);
}
public static AccessToken getAccessTokenFrom(TokenResponse tokenResponse) throws Exception {
- return getAccessTokenFrom(tokenResponse, RefreshToken.class);
+ return getAccessTokenFrom(tokenResponse, AccessToken.class);
+ }
+
+ public static AccessToken getAccessTokenFrom(String authorizationHeaderOrBase64EncodedJWT) throws Exception {
+ return getAccessTokenFrom(authorizationHeaderOrBase64EncodedJWT.matches("[b|B]earer ")
+ ? authorizationHeaderOrBase64EncodedJWT.substring("bearer ".length())
+ : authorizationHeaderOrBase64EncodedJWT, AccessToken.class);
}
private static T getAccessTokenFrom(TokenResponse tokenResponse, Class clazz) throws Exception {
- return mapper.readValue(getDecodedPayload(tokenResponse.getAccessToken()), clazz);
+ return getAccessTokenFrom(tokenResponse.getAccessToken(), clazz);
+ }
+
+ private static T getAccessTokenFrom(String accessToken, Class clazz) throws Exception {
+ return mapper.readValue(getDecodedPayload(accessToken), clazz);
}
public static String getRefreshTokenPayloadStringFrom(TokenResponse tokenResponse) throws Exception {
return getRefreshTokenPayloadStringFrom(tokenResponse, true);
}
- public static String getRefreshTokenPayloadStringFrom(TokenResponse tokenResponse, boolean prettyPrint) throws Exception {
+ public static String getRefreshTokenPayloadStringFrom(TokenResponse tokenResponse, boolean prettyPrint)
+ throws Exception {
+
return toJSONString(getRefreshTokenFrom(tokenResponse, Object.class), prettyPrint);
}
@@ -82,16 +95,50 @@ public class ModelUtils {
}
}
+ public static byte[] getDecodedHeader(String value) {
+ return getBase64Decoded(getEncodedHeader(value));
+ }
+
public static String getEncodedHeader(String encodedJWT) {
return splitAndGet(encodedJWT, 0);
}
+ public static byte[] getDecodedPayload(String value) {
+ return getBase64Decoded(getEncodedPayload(value));
+ }
+
public static String getEncodedPayload(String encodedJWT) {
return splitAndGet(encodedJWT, 1);
}
+ public static byte[] getDecodedSignature(String value) {
+ return getBase64Decoded(getEncodedSignature(value));
+ }
+
public static String getEncodedSignature(String encodedJWT) {
return splitAndGet(encodedJWT, 2);
}
+ public static String getClientIdFromToken(AccessToken accessToken) {
+ String clientId;
+ logger.debug("Client id not provided, using authorized party field (azp)");
+ clientId = accessToken.getIssuedFor();
+ if (clientId == null) {
+ logger.debug("Issued for field (azp) not present, getting first of the audience field (aud)");
+ clientId = getFirstAudienceNoAccount(accessToken);
+ }
+ return clientId;
+ }
+
+ private static String getFirstAudienceNoAccount(AccessToken accessToken) {
+ // Trying to get it from the token's audience ('aud' field), getting the first except the 'account'
+ List tokenAud = Arrays.asList(accessToken.getAudience());
+ tokenAud.remove(ACCOUNT_AUDIENCE_RESOURCE);
+ if (tokenAud.size() > 0) {
+ return tokenAud.iterator().next();
+ } else {
+ // Setting it to empty string to avoid NPE in encoding
+ return "";
+ }
+ }
}
diff --git a/src/main/java/org/gcube/common/keycloak/model/OIDCConstants.java b/src/main/java/org/gcube/common/keycloak/model/OIDCConstants.java
new file mode 100644
index 0000000..45212dd
--- /dev/null
+++ b/src/main/java/org/gcube/common/keycloak/model/OIDCConstants.java
@@ -0,0 +1,14 @@
+package org.gcube.common.keycloak.model;
+
+public class OIDCConstants {
+
+ public static final String PERMISSION_PARAMETER = "permission";
+ public static final String GRANT_TYPE_PARAMETER = "grant_type";
+ public static final String UMA_TOKEN_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:uma-ticket";
+ public static final String AUDIENCE_PARAMETER = "audience";
+ public static final String REFRESH_TOKEN_GRANT_TYPE = "refresh_token";
+ public static final String REFRESH_TOKEN_PARAMETER = "refresh_token";
+ public static final String CLIENT_ID_PARAMETER = "client_id";
+ public static final String CLIENT_SECRET_PARAMETER = "client_secret";
+
+}
diff --git a/src/test/java/org/gcube/common/keycloak/TestKeycloakClient.java b/src/test/java/org/gcube/common/keycloak/TestKeycloakClient.java
index 2fca755..fe1d097 100644
--- a/src/test/java/org/gcube/common/keycloak/TestKeycloakClient.java
+++ b/src/test/java/org/gcube/common/keycloak/TestKeycloakClient.java
@@ -8,10 +8,13 @@ import org.gcube.common.scope.api.ScopeProvider;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
+import org.junit.FixMethodOrder;
import org.junit.Test;
+import org.junit.runners.MethodSorters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestKeycloakClient {
protected static final Logger logger = LoggerFactory.getLogger(TestKeycloakClient.class);
@@ -21,6 +24,8 @@ public class TestKeycloakClient {
private static final String CLIENT_SECRET = "38f76152-2b7c-418f-9b67-66f4cc2f401e";
private static final String TEST_AUDIENCE = "conductor-server";
+ private static TokenResponse tr = null;
+
@Before
public void setUp() throws Exception {
ScopeProvider.instance.set("/gcube");
@@ -31,7 +36,7 @@ public class TestKeycloakClient {
}
@Test
- public void testEndpointDiscovery() throws Exception {
+ public void test1EndpointDiscovery() throws Exception {
logger.info("Start testing Keycloak endpoint discovery...");
URL url = KeycloakClientFactory.newInstance().findTokenEndpointURL();
Assert.assertNotNull(url);
@@ -40,31 +45,38 @@ public class TestKeycloakClient {
}
@Test
- public void testQueryUMATokenWithDiscoveryInCurrentScope() throws Exception {
+ public void test2QueryUMATokenWithDiscoveryInCurrentScope() throws Exception {
logger.info("Start testing query UMA token from Keycloak with endpoint discovery and current scope...");
- TokenResponse tr = KeycloakClientFactory.newInstance().queryUMAToken(CLIENT_ID, CLIENT_SECRET, null);
+ tr = KeycloakClientFactory.newInstance().queryUMAToken(CLIENT_ID, CLIENT_SECRET, null);
TestModels.checkTokenResponse(tr);
TestModels.checkAccessToken(ModelUtils.getAccessTokenFrom(tr), "service-account-" + CLIENT_ID);
}
@Test
- public void testQueryUMATokenWithDiscovery() throws Exception {
+ public void test3QueryUMATokenWithDiscovery() throws Exception {
logger.info("Start testing query UMA token from Keycloak with endpoint discovery...");
- TokenResponse tr = KeycloakClientFactory.newInstance().queryUMAToken(CLIENT_ID, CLIENT_SECRET, TEST_AUDIENCE,
- null);
+ tr = KeycloakClientFactory.newInstance().queryUMAToken(CLIENT_ID, CLIENT_SECRET, TEST_AUDIENCE, null);
+ TestModels.checkTokenResponse(tr);
+ TestModels.checkAccessToken(ModelUtils.getAccessTokenFrom(tr), "service-account-" + CLIENT_ID);
+ }
+
+ @Test
+ public void test4QueryUMAToken() throws Exception {
+ logger.info("Start testing query UMA token from Keycloak with URL...");
+ tr = KeycloakClientFactory.newInstance().queryUMAToken(new URL(DEV_ENDPOINT), CLIENT_ID, CLIENT_SECRET,
+ TEST_AUDIENCE, null);
TestModels.checkTokenResponse(tr);
TestModels.checkAccessToken(ModelUtils.getAccessTokenFrom(tr), "service-account-" + CLIENT_ID);
}
@Test
- public void testQueryUMAToken() throws Exception {
- logger.info("Start testing query UMA token from Keycloak with URL...");
- TokenResponse tr = KeycloakClientFactory.newInstance()
- .queryUMAToken(new URL(DEV_ENDPOINT), CLIENT_ID, CLIENT_SECRET, TEST_AUDIENCE, null);
-
- TestModels.checkTokenResponse(tr);
- TestModels.checkAccessToken(ModelUtils.getAccessTokenFrom(tr), "service-account-" + CLIENT_ID);
+ public void test5RefreshTokenWithDiscovery() throws Exception {
+ logger.info("Start testing refresh UMA token from Keycloak with endpoint discovery...");
+ TokenResponse refreshedTR = KeycloakClientFactory.newInstance().refreshToken(CLIENT_ID, CLIENT_SECRET, tr);
+ TestModels.checkTokenResponse(refreshedTR);
+ TestModels.checkAccessToken(ModelUtils.getAccessTokenFrom(refreshedTR), "service-account-" + CLIENT_ID);
+ TestModels.checkRefreshToken(ModelUtils.getRefreshTokenFrom(refreshedTR));
}
}
diff --git a/src/test/java/org/gcube/common/keycloak/TestModels.java b/src/test/java/org/gcube/common/keycloak/TestModels.java
index 511b3bc..b1967ef 100644
--- a/src/test/java/org/gcube/common/keycloak/TestModels.java
+++ b/src/test/java/org/gcube/common/keycloak/TestModels.java
@@ -3,17 +3,20 @@ package org.gcube.common.keycloak;
import java.io.File;
import org.gcube.com.fasterxml.jackson.databind.ObjectMapper;
-import org.gcube.common.keycloak.model.TokenResponse;
import org.gcube.common.keycloak.model.AccessToken;
import org.gcube.common.keycloak.model.ModelUtils;
import org.gcube.common.keycloak.model.RefreshToken;
+import org.gcube.common.keycloak.model.TokenResponse;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
+import org.junit.FixMethodOrder;
import org.junit.Test;
+import org.junit.runners.MethodSorters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestModels {
protected static final Logger logger = LoggerFactory.getLogger(TestModels.class);
@@ -56,6 +59,22 @@ public class TestModels {
checkAccessToken(at, null);
}
+ @Test
+ public void testRemoveBearerPrefixInHeader() throws Exception {
+ TokenResponse tr = new ObjectMapper().readValue(new File("src/test/resources/oidc-token-response.json"),
+ TokenResponse.class);
+
+ AccessToken at1 = ModelUtils.getAccessTokenFrom(tr.getAccessToken());
+ AccessToken at2 = ModelUtils.getAccessTokenFrom("Bearer " + tr.getAccessToken());
+ AccessToken at3 = ModelUtils.getAccessTokenFrom("bearer " + tr.getAccessToken());
+
+ checkAccessToken(at1, null);
+ checkAccessToken(at2, null);
+ checkAccessToken(at3, null);
+ Assert.assertEquals(ModelUtils.toJSONString(at1), ModelUtils.toJSONString(at2));
+ Assert.assertEquals(ModelUtils.toJSONString(at2), ModelUtils.toJSONString(at3));
+ }
+
@Test
public void testUMARefreshToken() throws Exception {
logger.info("Start testing refresh token object binding...");
@@ -86,4 +105,5 @@ public class TestModels {
Assert.assertNotNull(rt.getOtherClaims());
Assert.assertNotNull(rt.getAudience());
}
+
}