Compare commits

...

19 Commits

Author SHA1 Message Date
Mauro Mugnaini 45c52f1d56
Releasing version `2.1.0` 2024-05-07 16:56:05 +02:00
Mauro Mugnaini a4c5de4e9e
Implemented custom deserializer that uses `gcube-jackson-databind` and removes completely the dependency with `jjson-*` deserializers 2024-05-07 16:51:34 +02:00
Mauro Mugnaini e0b165c491
Releasing version `2.1.0` 2024-05-06 18:11:29 +02:00
Mauro Mugnaini 1cbfa034e7
Testing offline token exchange with exception only (scope without specific value) 2024-05-06 18:11:11 +02:00
Mauro Mugnaini 22013667d1
Token exchage for an offline token now raises an IllegalArgumetException if the original token not contains `offline_access` within its scopes, required by the `v24.0.2` version of the Keycloak 2024-05-06 18:10:26 +02:00
Mauro Mugnaini dfb35bad62
Added javadoc for token exchange methods and exchage for an offline token now raises an IllegalArgumetException if the original token not contains `offline_access` within its scopes, required by the `v24.0.2` version of the Keycloak 2024-05-06 18:09:40 +02:00
Mauro Mugnaini eabd708631
Temporary disabling exchange for offline token since with new keycloak original token should be issued with `offline` scope to work and has still to be inroduced the possibility to do it 2024-05-03 12:13:41 +02:00
Mauro Mugnaini 899cf13afd
Temporary disabling exchange for offline token since with new keycloak original token should be issued with `offline` scope to work and has still to be inroduced the possibility to do it 2024-05-03 12:10:07 +02:00
Mauro Mugnaini 49af6590f7
Fixed javadocs 2024-05-03 12:07:42 +02:00
Mauro Mugnaini ca0423cdf2
Changed to `runtime` `jjwt-impl` dependnecy and moved to `jjwt-gson` as `provided` seems to solve the issue #27377 2024-05-03 12:07:19 +02:00
Mauro Mugnaini 027803b7e9
Added also `jackson-core` and `jackson-annotations` to the exclusions of `jjwt-impl` dependency 2024-05-02 18:14:39 +02:00
Mauro Mugnaini 0dbf1c0c95
Added explicit declaration of `jackson-databind `2,)` to `provided`, also added to exclusions of `jjwt-impl` 2024-05-02 11:37:32 +02:00
Mauro Mugnaini 5938bf4af8
Replaced `auth0` lib with `jjwt` by `io.jsonwebtoken` that doesn't require jackson at runtime if not used 2024-04-30 20:13:30 +02:00
Mauro Mugnaini e339be5083
Added support to JWK endpoint and key sets retrieve to take info about configured key algorithms 2024-04-30 18:31:07 +02:00
Mauro Mugnaini 7d98fbaa16
Overloaded methods to disable token expiration, generalized public key generation providing key algorithm and added support of RS384 and RS512 signature algorithms, defaulting to RS256 if not specified 2024-04-30 18:29:21 +02:00
Mauro Mugnaini 8c009b9a8d
Renamed method `isSignatureValid()` to `isValid()` since it tests also other aspects (exipration, not before, etc...). 2024-04-30 12:55:04 +02:00
Mauro Mugnaini 23f387f832
Added JWT digital signature verification by using the RSA public key of the realm on server. Uses `java-jwt` library by Auth0 [#27340] 2024-04-30 11:48:22 +02:00
Mauro Mugnaini 726291ca55
Added custom base URL set via factory (not automatically working cross environments) [#27234]
Better tests for exchange-token features
2024-04-22 17:50:00 +02:00
Mauro Mugnaini 35c913db02
Added `token-exchange` support, also with `offline-token` scope, and methods to add extra headers during the OIDC token requests. 2024-04-19 17:26:40 +02:00
26 changed files with 1905 additions and 319 deletions

View File

@ -2,6 +2,11 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
# Changelog for "keycloak-client"
## [v2.1.0]
- Added `token-exchange` support, also with `offline-token` scope, and methods to add extra headers during the OIDC token requests (#27099).
- Added custom base URL set via factory (not automatically working cross environments) [#27234].
- Added JWKS server configuration retrieval, realm's info (as `PublishedRealmRepresentation` JSON containing public key) and JWT digital signature verification by using the RSA public key of the realm on server. It uses the `jjwt` library by `io.jsonwebtoken` [#27340]
## [v2.0.0]
- Removed the discovery functionality to be compatible with SmartGears.v4 and moved to the new library `keycloak-client-legacy-is` that will provide the backward compatibility. (#23478).
- Fixed typo in `AccessToken` class for `setAccessToken(..)` method (#23654)

31
pom.xml
View File

@ -7,13 +7,12 @@
<parent>
<artifactId>maven-parent</artifactId>
<groupId>org.gcube.tools</groupId>
<version>1.1.0</version>
<relativePath />
<version>1.2.0</version>
</parent>
<groupId>org.gcube.common</groupId>
<artifactId>keycloak-client</artifactId>
<version>2.0.0</version>
<version>2.1.0</version>
<dependencyManagement>
<dependencies>
@ -28,11 +27,20 @@
</dependencyManagement>
<scm>
<connection>scm:git:https://code-repo.d4science.org/gCubeSystem/${project.artifactId}.git</connection>
<developerConnection>scm:git:https://code-repo.d4science.org/gCubeSystem/${project.artifactId}.git</developerConnection>
<connection>
scm:git:https://code-repo.d4science.org/gCubeSystem/${project.artifactId}.git</connection>
<developerConnection>
scm:git:https://code-repo.d4science.org/gCubeSystem/${project.artifactId}.git</developerConnection>
<url>https://code-repo.d4science.org/gCubeSystem/${project.artifactId}</url>
</scm>
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<jjwt.version>0.12.5</jjwt.version>
</properties>
<dependencies>
<dependency>
@ -60,6 +68,19 @@
<artifactId>gxJRS</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>

View File

@ -1,18 +1,26 @@
package org.gcube.common.keycloak;
import static org.gcube.common.keycloak.model.OIDCConstants.ACCESS_TOKEN_TOKEN_TYPE;
import static org.gcube.common.keycloak.model.OIDCConstants.AUDIENCE_PARAMETER;
import static org.gcube.common.keycloak.model.OIDCConstants.CLIENT_CREDENTIALS_GRANT_TYPE;
import static org.gcube.common.keycloak.model.OIDCConstants.CLIENT_ID_PARAMETER;
import static org.gcube.common.keycloak.model.OIDCConstants.CLIENT_SECRET_PARAMETER;
import static org.gcube.common.keycloak.model.OIDCConstants.GRANT_TYPE_PARAMETER;
import static org.gcube.common.keycloak.model.OIDCConstants.OFFLINE_ACCESS_SCOPE;
import static org.gcube.common.keycloak.model.OIDCConstants.PASSWORD_GRANT_TYPE;
import static org.gcube.common.keycloak.model.OIDCConstants.PASSWORD_PARAMETER;
import static org.gcube.common.keycloak.model.OIDCConstants.PERMISSION_PARAMETER;
import static org.gcube.common.keycloak.model.OIDCConstants.REFRESH_TOKEN_GRANT_TYPE;
import static org.gcube.common.keycloak.model.OIDCConstants.REFRESH_TOKEN_PARAMETER;
import static org.gcube.common.keycloak.model.OIDCConstants.REFRESH_TOKEN_TOKEN_TYPE;
import static org.gcube.common.keycloak.model.OIDCConstants.REQUESTED_TOKEN_TYPE_PARAMETER;
import static org.gcube.common.keycloak.model.OIDCConstants.SCOPE_PARAMETER;
import static org.gcube.common.keycloak.model.OIDCConstants.SUBJECT_TOKEN_PARAMETER;
import static org.gcube.common.keycloak.model.OIDCConstants.SUBJECT_TOKEN_TYPE_PARAMETER;
import static org.gcube.common.keycloak.model.OIDCConstants.TOKEN_EXCHANGE_GRANT_TYPE;
import static org.gcube.common.keycloak.model.OIDCConstants.TOKEN_PARAMETER;
import static org.gcube.common.keycloak.model.OIDCConstants.UMA_TOKEN_GRANT_TYPE;
import static org.gcube.common.keycloak.model.OIDCConstants.PASSWORD_GRANT_TYPE;
import static org.gcube.common.keycloak.model.OIDCConstants.USERNAME_PARAMETER;
import static org.gcube.common.keycloak.model.OIDCConstants.PASSWORD_PARAMETER;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
@ -20,6 +28,7 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Base64;
@ -29,9 +38,14 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.gcube.common.gxhttp.util.ContentUtils;
import org.gcube.common.gxrest.request.GXHTTPStringRequest;
import org.gcube.common.gxrest.response.inbound.GXInboundResponse;
import org.gcube.common.gxrest.response.inbound.JsonUtils;
import org.gcube.common.keycloak.model.AccessToken;
import org.gcube.common.keycloak.model.JSONWebKeySet;
import org.gcube.common.keycloak.model.ModelUtils;
import org.gcube.common.keycloak.model.PublishedRealmRepresentation;
import org.gcube.common.keycloak.model.TokenIntrospectionResponse;
import org.gcube.common.keycloak.model.TokenResponse;
import org.slf4j.Logger;
@ -42,9 +56,22 @@ public class DefaultKeycloakClient implements KeycloakClient {
protected static Logger logger = LoggerFactory.getLogger(KeycloakClient.class);
protected final static String AUTHORIZATION_HEADER = "Authorization";
protected final static String D4S_CONTEXT_HEADER_NAME = "X-D4Science-Context";
public static final String BASE_URL = "https://url.d4science.org/auth/realms/";
public static final String DEFAULT_BASE_URL = "https://url.d4science.org/auth/realms/";
private String customBaseURL = null;
public void setCustomBaseURL(String customBaseURL) {
if (customBaseURL == null || customBaseURL.endsWith("/")) {
this.customBaseURL = customBaseURL;
} else {
this.customBaseURL = customBaseURL += "/";
}
}
public String getCustomBaseURL() {
return customBaseURL;
}
@Override
public URL getRealmBaseURL(String context) throws KeycloakClientException {
@ -53,20 +80,36 @@ public class DefaultKeycloakClient implements KeycloakClient {
@Override
public URL getRealmBaseURL(String context, String realm) throws KeycloakClientException {
String urlString = BASE_URL + realm + "/";
if (!context.startsWith(PROD_ROOT_SCOPE)) {
String root = context.split("/")[1];
urlString = urlString.replace("url", "url." + root.replaceAll("\\.", "-"));
String realmBaseURLString = null;
if (getCustomBaseURL() != null) {
realmBaseURLString = getCustomBaseURL() + realm + "/";
} else {
realmBaseURLString = DEFAULT_BASE_URL + realm + "/";
if (!context.startsWith(PROD_ROOT_SCOPE)) {
String root = checkContext(context).split("/")[1];
realmBaseURLString = realmBaseURLString.replace("url", "url." + root.replaceAll("\\.", "-"));
}
}
try {
return new URL(urlString);
return new URL(realmBaseURLString);
} catch (MalformedURLException e) {
// That should be almost impossible
logger.warn("Cannot create base URL from string: {}", urlString, e);
logger.error("Cannot create base URL from string: {}", realmBaseURLString, e);
return null;
}
}
private static String checkContext(String context) {
if (!context.startsWith("/")) {
try {
logger.trace("Context was provided in URL encoded form, decoding it");
return URLDecoder.decode(context, "UTF-8");
} catch (UnsupportedEncodingException e) {
logger.error("Cannot URL decode 'context'", e);
}
}
return context;
}
@Override
public URL getTokenEndpointURL(URL realmBaseURL) throws KeycloakClientException {
logger.debug("Constructing token endpoint URL starting from base URL: {}", realmBaseURL);
@ -80,7 +123,24 @@ public class DefaultKeycloakClient implements KeycloakClient {
logger.debug("Constructed token URL is: {}", tokenURL);
return tokenURL;
} catch (MalformedURLException e) {
throw new KeycloakClientException("Cannot constructs toke URL from base URL: " + realmBaseURL, e);
throw new KeycloakClientException("Cannot constructs token URL from base URL: " + realmBaseURL, e);
}
}
@Override
public URL getJWKEndpointURL(URL realmBaseURL) throws KeycloakClientException {
logger.debug("Constructing JWK endpoint URL starting from base URL: {}", realmBaseURL);
try {
URL jwkURL = null;
if (realmBaseURL.getPath().endsWith("/")) {
jwkURL = new URL(realmBaseURL, OPEN_ID_URI_PATH + "/" + JWK_URI_PATH);
} else {
jwkURL = new URL(realmBaseURL.toString() + "/" + OPEN_ID_URI_PATH + "/" + JWK_URI_PATH);
}
logger.debug("Constructed JWK URL is: {}", jwkURL);
return jwkURL;
} catch (MalformedURLException e) {
throw new KeycloakClientException("Cannot constructs JWK URL from base URL: " + realmBaseURL, e);
}
}
@ -99,7 +159,24 @@ public class DefaultKeycloakClient implements KeycloakClient {
logger.debug("Constructed introspection URL is: {}", tokenURL);
return tokenURL;
} catch (MalformedURLException e) {
throw new KeycloakClientException("Cannot constructs toke URL from base URL: " + realmBaseURL, e);
throw new KeycloakClientException("Cannot constructs introspection URL from base URL: " + realmBaseURL, e);
}
}
@Override
public URL getAvatarEndpointURL(URL realmBaseURL) throws KeycloakClientException {
logger.debug("Constructing token endpoint URL starting from base URL: {}", realmBaseURL);
try {
URL tokenURL = null;
if (realmBaseURL.getPath().endsWith("/")) {
tokenURL = new URL(realmBaseURL, AVATAR_URI_PATH);
} else {
tokenURL = new URL(realmBaseURL.toString() + "/" + AVATAR_URI_PATH);
}
logger.debug("Constructed avatar URL is: {}", tokenURL);
return tokenURL;
} catch (MalformedURLException e) {
throw new KeycloakClientException("Cannot constructs avatar URL from base URL: " + realmBaseURL, e);
}
}
@ -121,11 +198,90 @@ public class DefaultKeycloakClient implements KeycloakClient {
}
}
@Override
public PublishedRealmRepresentation getRealmInfo(URL realmURL) throws KeycloakClientException {
try {
return JsonUtils.fromJson(ContentUtils.toByteArray(realmURL.openStream()),
PublishedRealmRepresentation.class);
} catch (Exception e) {
throw new KeycloakClientException("Getting realm's info", e);
}
}
@Override
public JSONWebKeySet getRealmJSONWebKeySet(URL jwkURL) throws KeycloakClientException {
try {
return JsonUtils.fromJson(ContentUtils.toByteArray(jwkURL.openStream()), JSONWebKeySet.class);
} catch (Exception e) {
throw new KeycloakClientException("Getting realm's JWK", e);
}
}
@Override
public TokenResponse queryOIDCToken(String context, String clientId, String clientSecret)
throws KeycloakClientException {
return queryOIDCTokenWithContext(context, clientId, clientSecret, null);
return queryOIDCToken(context, clientId, clientSecret, null);
}
@Override
public TokenResponse queryOIDCToken(String context, String clientId, String clientSecret,
Map<String, String> extraHeaders) throws KeycloakClientException {
return queryOIDCToken(getTokenEndpointURL(getRealmBaseURL(context)), clientId, clientSecret, extraHeaders);
}
@Override
public TokenResponse queryOIDCToken(URL tokenURL, String clientId, String clientSecret)
throws KeycloakClientException {
return queryOIDCToken(tokenURL, clientId, clientSecret, null);
}
@Override
public TokenResponse queryOIDCToken(URL tokenURL, String clientId, String clientSecret,
Map<String, String> extraHeaders) throws KeycloakClientException {
return queryOIDCTokenWithContext(tokenURL, clientId, clientSecret, null, extraHeaders);
}
@Override
public TokenResponse queryOIDCToken(String context, String authorization) throws KeycloakClientException {
return queryOIDCTokenWithContext(context, authorization, null);
}
@Override
public TokenResponse queryOIDCToken(URL tokenURL, String authorization) throws KeycloakClientException {
return queryOIDCToken(tokenURL, authorization, (Map<String, String>) null);
}
@Override
public TokenResponse queryOIDCToken(String context, String authorization, Map<String, String> extraHeaders)
throws KeycloakClientException {
return queryOIDCToken(getTokenEndpointURL(getRealmBaseURL(context)), authorization, extraHeaders);
}
@Override
public TokenResponse queryOIDCToken(URL tokenURL, String authorization, Map<String, String> extraHeaders)
throws KeycloakClientException {
return queryOIDCTokenWithContext(tokenURL, authorization, null, extraHeaders);
}
@Override
public TokenResponse queryOIDCTokenOfUser(String context, String clientId, String clientSecret, String username,
String password) throws KeycloakClientException {
return queryOIDCTokenOfUser(context, clientId, clientSecret, username, password, null);
}
@Override
public TokenResponse queryOIDCTokenOfUser(String context, String clientId, String clientSecret, String username,
String password, Map<String, String> extraHeaders) throws KeycloakClientException {
return queryOIDCTokenOfUserWithContext(context, clientId, clientSecret, username, password, null, extraHeaders);
}
@Override
@ -137,39 +293,70 @@ public class DefaultKeycloakClient implements KeycloakClient {
}
@Override
public TokenResponse queryOIDCTokenOfUser(String context, String clientId, String clientSecret, String username,
String password) throws KeycloakClientException {
return queryOIDCTokenOfUserWithContext(context, clientId, clientSecret, username, password, null);
}
@Override
public TokenResponse queryOIDCTokenOfUserWithContext(String context, String clientId, String clientSecret,
String username, String password, String audience) throws KeycloakClientException {
return queryOIDCTokenOfUserWithContext(getTokenEndpointURL(getRealmBaseURL(context)), clientId, clientSecret,
username, password, null);
}
@Override
public TokenResponse queryOIDCToken(URL tokenURL, String clientId, String clientSecret)
public TokenResponse queryOIDCTokenWithContext(String context, String authorization, String audience)
throws KeycloakClientException {
return queryOIDCTokenWithContext(tokenURL, clientId, clientSecret, null);
return queryOIDCTokenWithContext(getTokenEndpointURL(getRealmBaseURL(context)), authorization, audience);
}
@Override
public TokenResponse queryOIDCTokenWithContext(URL tokenURL, String clientId, String clientSecret,
String audience) throws KeycloakClientException {
return queryOIDCTokenWithContext(tokenURL, constructBasicAuthenticationHeader(clientId, clientSecret),
audience);
return queryOIDCTokenWithContext(tokenURL, clientId, clientSecret, audience, null);
}
protected String constructBasicAuthenticationHeader(String clientId, String clientSecret) {
@Override
public TokenResponse queryOIDCTokenWithContext(String context, String clientId, String clientSecret,
String audience, Map<String, String> extraHeaders) throws KeycloakClientException {
return queryOIDCTokenWithContext(getTokenEndpointURL(getRealmBaseURL(context)), clientId, clientSecret,
audience, extraHeaders);
}
@Override
public TokenResponse queryOIDCTokenWithContext(URL tokenURL, String clientId, String clientSecret, String audience,
Map<String, String> extraHeaders) throws KeycloakClientException {
return queryOIDCTokenWithContext(tokenURL, constructBasicAuthenticationHeader(clientId, clientSecret),
audience, extraHeaders);
}
@Override
public TokenResponse queryOIDCTokenWithContext(String context, String authorization, String audience,
Map<String, String> extraHeaders) throws KeycloakClientException {
return queryOIDCTokenWithContext(getTokenEndpointURL(getRealmBaseURL(context)), authorization, audience,
extraHeaders);
}
@Override
public TokenResponse queryOIDCTokenWithContext(URL tokenURL, String authorization, String audience)
throws KeycloakClientException {
return queryOIDCTokenWithContext(tokenURL, authorization, audience, (Map<String, String>) null);
}
protected static String constructBasicAuthenticationHeader(String clientId, String clientSecret) {
return "Basic " + Base64.getEncoder().encodeToString((clientId + ":" + clientSecret).getBytes());
}
@Override
public TokenResponse queryOIDCTokenOfUserWithContext(String context, String clientId, String clientSecret,
String username, String password, String audience) throws KeycloakClientException {
return queryOIDCTokenOfUserWithContext(context, clientId, clientSecret, username, password, audience,
(Map<String, String>) null);
}
@Override
public TokenResponse queryOIDCTokenOfUserWithContext(String context, String clientId, String clientSecret,
String username, String password, String audience, Map<String, String> extraHeaders)
throws KeycloakClientException {
return queryOIDCTokenOfUserWithContext(getTokenEndpointURL(getRealmBaseURL(context)), clientId, clientSecret,
username, password, audience, extraHeaders);
}
@Override
public TokenResponse queryOIDCTokenOfUserWithContext(String context, String authorization, String username,
String password, String audience) throws KeycloakClientException {
@ -179,36 +366,55 @@ public class DefaultKeycloakClient implements KeycloakClient {
}
@Override
public TokenResponse queryOIDCToken(String context, String authorization) throws KeycloakClientException {
return queryOIDCTokenWithContext(context, authorization, null);
}
public TokenResponse queryOIDCTokenOfUserWithContext(String context, String authorization, String username,
String password, String audience, Map<String, String> extraHeaders) throws KeycloakClientException {
@Override
public TokenResponse queryOIDCTokenWithContext(String context, String authorization, String audience)
throws KeycloakClientException {
return queryOIDCTokenWithContext(getTokenEndpointURL(getRealmBaseURL(context)), authorization, audience);
return queryOIDCTokenOfUserWithContext(getTokenEndpointURL(getRealmBaseURL(context)), authorization, username,
password, audience, extraHeaders);
}
@Override
public TokenResponse queryOIDCTokenOfUserWithContext(URL tokenURL, String clientId, String clientSecret,
String username, String password, String audience) throws KeycloakClientException {
return queryOIDCTokenOfUserWithContext(tokenURL, clientId, clientSecret, username, password, audience,
(Map<String, String>) null);
}
@Override
public TokenResponse queryOIDCTokenOfUserWithContext(URL tokenURL, String clientId, String clientSecret,
String username, String password, String audience, Map<String, String> extraHeaders)
throws KeycloakClientException {
return queryOIDCTokenOfUserWithContext(tokenURL, constructBasicAuthenticationHeader(clientId, clientSecret),
username, password, audience);
username, password, audience, extraHeaders);
}
@Override
public TokenResponse queryOIDCTokenOfUserWithContext(URL tokenURL, String authorization, String username,
String password, String audience) throws KeycloakClientException {
return queryOIDCTokenOfUserWithContext(tokenURL, authorization, username, password, audience,
(Map<String, String>) null);
}
@Override
public TokenResponse queryOIDCTokenOfUserWithContext(URL tokenURL, String authorization, String username,
String password, String audience, Map<String, String> extraHeaders) throws KeycloakClientException {
Map<String, List<String>> params = new HashMap<>();
params.put(GRANT_TYPE_PARAMETER, Arrays.asList(PASSWORD_GRANT_TYPE));
params.put(USERNAME_PARAMETER, Arrays.asList(username));
params.put(PASSWORD_PARAMETER, Arrays.asList(password));
// params.put(SCOPE_PARAMETER, Arrays.asList("openid profile " + OFFLINE_ACCESS_SCOPE));
Map<String, String> headers = new HashMap<>();
logger.debug("Adding authorization header as: {}", authorization);
headers.put(AUTHORIZATION_HEADER, authorization);
if (extraHeaders != null) {
logger.debug("Adding provided extra headers: {}", extraHeaders);
headers.putAll(extraHeaders);
}
if (audience != null) {
logger.debug("Adding d4s context header as: {}", audience);
@ -219,12 +425,8 @@ public class DefaultKeycloakClient implements KeycloakClient {
}
@Override
public TokenResponse queryOIDCToken(URL tokenURL, String authorization) throws KeycloakClientException {
return queryOIDCTokenWithContext(tokenURL, authorization, null);
}
@Override
public TokenResponse queryOIDCTokenWithContext(URL tokenURL, String authorization, String audience)
public TokenResponse queryOIDCTokenWithContext(URL tokenURL, String authorization, String audience,
Map<String, String> extraHeaders)
throws KeycloakClientException {
logger.debug("Querying OIDC token from Keycloak server with URL: {}", tokenURL);
@ -257,7 +459,7 @@ public class DefaultKeycloakClient implements KeycloakClient {
return queryUMAToken(tokenURL, constructBeareAuthenticationHeader(oidcTokenResponse), audience, permissions);
}
protected String constructBeareAuthenticationHeader(TokenResponse oidcTokenResponse) {
protected static String constructBeareAuthenticationHeader(TokenResponse oidcTokenResponse) {
return "Bearer " + oidcTokenResponse.getAccessToken();
}
@ -327,30 +529,47 @@ public class DefaultKeycloakClient implements KeycloakClient {
protected TokenResponse performRequest(URL tokenURL, Map<String, String> headers, Map<String, List<String>> params)
throws KeycloakClientException {
if (tokenURL == null) {
return performRequest(TokenResponse.class, tokenURL, headers, params);
}
protected <T> T performRequest(Class<T> returnObjectClass, URL url, Map<String, String> headers,
Map<String, List<String>> params)
throws KeycloakClientException {
if (url == null) {
throw new KeycloakClientException("Token URL must be not null");
}
if (!headers.containsKey(AUTHORIZATION_HEADER) || "".equals(headers.get(AUTHORIZATION_HEADER))) {
throw new KeycloakClientException("Authorization must be not null nor empty");
}
// Constructing request object
GXHTTPStringRequest request;
try {
String queryString = params.entrySet().stream()
.flatMap(p -> p.getValue().stream().map(v -> p.getKey() + "=" + v))
.reduce((p1, p2) -> p1 + "&" + p2).orElse("");
String queryString = "";
if (params != null) {
queryString = params.entrySet().stream()
.flatMap(p -> p.getValue().stream().map(v -> p.getKey() + "=" + v))
.reduce((p1, p2) -> p1 + "&" + p2).orElse("");
} else {
if (logger.isDebugEnabled()) {
logger.debug("Params map is null");
}
}
logger.trace("Query string is {}", queryString);
logger.trace("Query string is: {}", queryString);
request = GXHTTPStringRequest.newRequest(tokenURL.toString())
request = GXHTTPStringRequest.newRequest(url.toString())
.header("Content-Type", "application/x-www-form-urlencoded").withBody(queryString);
safeSetAsExternalCallForOldAPI(request);
logger.trace("Adding provided headers: {}", headers);
for (String headerName : headers.keySet()) {
request.header(headerName, headers.get(headerName));
if (headers != null) {
logger.trace("Adding provided headers: {}", headers);
for (String headerName : headers.keySet()) {
request.header(headerName, headers.get(headerName));
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("HTTP headers map is null");
}
}
} catch (Exception e) {
throw new KeycloakClientException("Cannot construct the request object correctly", e);
@ -359,12 +578,13 @@ public class DefaultKeycloakClient implements KeycloakClient {
GXInboundResponse response;
try {
response = request.post();
// TODO: Fill a bug ticket for the gxJRS lib for JSON responses in case of not 2XX code (e.g. 403 error with JSON details in this case).
} catch (Exception e) {
throw new KeycloakClientException("Cannot send request correctly", e);
}
if (response.isSuccessResponse()) {
try {
return response.tryConvertStreamedContentFromJson(TokenResponse.class);
return response.tryConvertStreamedContentFromJson(returnObjectClass);
} catch (Exception e) {
throw new KeycloakClientException("Cannot construct token response object correctly", e);
}
@ -428,8 +648,7 @@ public class DefaultKeycloakClient implements KeycloakClient {
@Override
public TokenResponse refreshToken(String context, String clientId, String clientSecret,
String refreshTokenJWTString)
throws KeycloakClientException {
String refreshTokenJWTString) throws KeycloakClientException {
return refreshToken(getTokenEndpointURL(getRealmBaseURL(context)), clientId, clientSecret,
refreshTokenJWTString);
@ -453,47 +672,124 @@ public class DefaultKeycloakClient implements KeycloakClient {
logger.debug("Refreshing token from Keycloak server with URL: {}", tokenURL);
// Constructing request object
GXHTTPStringRequest request;
try {
Map<String, String> 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"));
Map<String, List<String>> params = new HashMap<>();
params.put(GRANT_TYPE_PARAMETER, Collections.singletonList(REFRESH_TOKEN_GRANT_TYPE));
params.put(REFRESH_TOKEN_PARAMETER, Collections.singletonList(refreshTokenJWTString));
params.put(CLIENT_ID_PARAMETER, Collections.singletonList(URLEncoder.encode(clientId, "UTF-8")));
if (clientSecret != null && !"".equals(clientSecret)) {
params.put(CLIENT_SECRET_PARAMETER, URLEncoder.encode(clientSecret, "UTF-8"));
params.put(CLIENT_SECRET_PARAMETER,
Collections.singletonList(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);
safeSetAsExternalCallForOldAPI(request);
} catch (Exception e) {
throw new KeycloakClientException("Cannot construct the request object correctly", e);
return performRequest(tokenURL, null, params);
} catch (UnsupportedEncodingException e) {
throw new KeycloakClientException("Cannot encode parameters", e);
}
GXInboundResponse response;
}
@Override
public TokenResponse exchangeTokenForAccessToken(String context, String oidcAccessToken, String clientId,
String clientSecret, String audience) throws KeycloakClientException {
return exchangeTokenForAccessToken(getTokenEndpointURL(getRealmBaseURL(context)), oidcAccessToken, clientId,
clientSecret, audience);
}
@Override
public TokenResponse exchangeTokenForAccessToken(URL tokenURL, String oidcAccessToken, String clientId,
String clientSecret, String audience) throws KeycloakClientException {
return exchangeToken(tokenURL, oidcAccessToken, clientId, clientSecret, audience, ACCESS_TOKEN_TOKEN_TYPE,
null);
}
@Override
public TokenResponse exchangeTokenForRefreshToken(String context, String oidcAccessToken, String clientId,
String clientSecret, String audience) throws KeycloakClientException {
return exchangeTokenForRefreshToken(getTokenEndpointURL(getRealmBaseURL(context)), oidcAccessToken, clientId,
clientSecret, audience);
}
@Override
public TokenResponse exchangeTokenForRefreshToken(URL tokenURL, String oidcAccessToken, String clientId,
String clientSecret, String audience) throws KeycloakClientException {
return exchangeToken(tokenURL, oidcAccessToken, clientId, clientSecret, audience, REFRESH_TOKEN_TOKEN_TYPE,
null);
}
@Override
public TokenResponse exchangeTokenForOfflineToken(String context, String oidcAccessToken, String clientId,
String clientSecret, String audience) throws IllegalArgumentException, KeycloakClientException {
return exchangeTokenForOfflineToken(getTokenEndpointURL(getRealmBaseURL(context)), oidcAccessToken, clientId,
clientSecret, audience);
}
@Override
public TokenResponse exchangeTokenForOfflineToken(URL tokenURL, String oidcAccessToken, String clientId,
String clientSecret, String audience) throws IllegalArgumentException, KeycloakClientException {
AccessToken at = null;
try {
response = request.post();
at = ModelUtils.getAccessTokenFrom(oidcAccessToken);
} catch (Exception e) {
throw new KeycloakClientException("Cannot send request correctly", e);
throw new IllegalArgumentException("Impossible to parse the access token as JSON", 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 refresh token", response.getHTTPCode(),
response.getHeaderFields()
.getOrDefault("content-type", Collections.singletonList("unknown/unknown")).get(0),
response.getMessage());
if (at.getScope().indexOf(OFFLINE_ACCESS_SCOPE) < 0) {
logger.info("Token to be exchanged doesn't contain 'offline_token' within scopes");
throw new IllegalArgumentException("Orignal access token doesn't contain the 'offline_token' scope");
}
return exchangeToken(tokenURL, oidcAccessToken, clientId, clientSecret, audience, REFRESH_TOKEN_TOKEN_TYPE,
OFFLINE_ACCESS_SCOPE);
}
/**
* Queries from the OIDC server an exchanged token by using provided access token, for the given audience (context),
* in URLEncoded form or not,
*
* @param tokenURL the token endpoint {@link URL} of the OIDC server
* @param oidcAccessToken the auth token (the access token URLEncoded by the "Bearer " string)
* @param clientId the client id
* @param clientSecret the client secret
* @param audience the audience (context) where to request the issuing of the ticket (URLEncoded)
* @param requestedTokenType the token type (e.g. <code>refresh</code>)
* @param scope the scope, optional can be <code>null</code>
* @return the issued exchanged token
* @throws KeycloakClientException if an error occurs, inspect the exception for details
*/
protected TokenResponse exchangeToken(URL tokenURL, String oidcAccessToken, String clientId, String clientSecret,
String audience, String requestedTokenType, String scope) throws KeycloakClientException {
if (audience == null || "".equals(audience)) {
throw new KeycloakClientException("Audience must be not null nor empty");
}
logger.debug("Exchanging token from Keycloak server with URL: {}", tokenURL);
Map<String, List<String>> params = new HashMap<>();
params.put(SUBJECT_TOKEN_PARAMETER, Arrays.asList(oidcAccessToken));
params.put(CLIENT_ID_PARAMETER, Arrays.asList(clientId));
params.put(CLIENT_SECRET_PARAMETER, Arrays.asList(clientSecret));
params.put(GRANT_TYPE_PARAMETER, Arrays.asList(TOKEN_EXCHANGE_GRANT_TYPE));
params.put(SUBJECT_TOKEN_TYPE_PARAMETER, Arrays.asList(ACCESS_TOKEN_TOKEN_TYPE));
params.put(REQUESTED_TOKEN_TYPE_PARAMETER, Arrays.asList(requestedTokenType));
if (scope != null) {
params.put(SCOPE_PARAMETER, Arrays.asList(scope));
}
try {
String audienceToSend = URLEncoder.encode(checkAudience(audience), "UTF-8");
params.put(AUDIENCE_PARAMETER, Arrays.asList(audienceToSend));
logger.trace("audience is {}", audienceToSend);
} catch (UnsupportedEncodingException e) {
logger.error("Can't URL encode audience: {}", audience, e);
}
return performRequest(tokenURL, null, params);
}
@Override
@ -522,44 +818,9 @@ public class DefaultKeycloakClient implements KeycloakClient {
logger.debug("Verifying access token against Keycloak server with URL: {}", introspectionURL);
// Constructing request object
GXHTTPStringRequest request;
try {
Map<String, String> params = new HashMap<>();
params.put(TOKEN_PARAMETER, accessTokenJWTString);
String queryString = params.entrySet().stream()
.map(p -> p.getKey() + "=" + p.getValue())
.reduce((p1, p2) -> p1 + "&" + p2).orElse("");
request = GXHTTPStringRequest.newRequest(introspectionURL.toString()).header("Content-Type",
"application/x-www-form-urlencoded").withBody(queryString);
safeSetAsExternalCallForOldAPI(request);
request = request.header("Authorization", constructBasicAuthenticationHeader(clientId, clientSecret));
} 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(TokenIntrospectionResponse.class);
} catch (Exception e) {
throw new KeycloakClientException("Cannot construct introspection response object correctly", e);
}
} else {
throw KeycloakClientException.create("Unable to get token introspection response", response.getHTTPCode(),
response.getHeaderFields()
.getOrDefault("content-type", Collections.singletonList("unknown/unknown")).get(0),
response.getMessage());
}
return performRequest(TokenIntrospectionResponse.class, introspectionURL,
Collections.singletonMap("Authorization", constructBasicAuthenticationHeader(clientId, clientSecret)),
Collections.singletonMap(TOKEN_PARAMETER, Collections.singletonList(accessTokenJWTString)));
}
@Override
@ -590,4 +851,46 @@ public class DefaultKeycloakClient implements KeycloakClient {
}
}
@Override
public byte[] getAvatarData(String context, TokenResponse tokenResponse) throws KeycloakClientException {
return getAvatarData(getAvatarEndpointURL(getRealmBaseURL(context)), tokenResponse);
}
@Override
public byte[] getAvatarData(URL avatarURL, TokenResponse tokenResponse) throws KeycloakClientException {
logger.debug("Getting user's avatar from URL: {}", avatarURL);
try {
GXHTTPStringRequest request;
try {
request = GXHTTPStringRequest.newRequest(avatarURL.toString());
safeSetAsExternalCallForOldAPI(request);
String authorization = constructBeareAuthenticationHeader(tokenResponse);
logger.debug("Adding authorization header as: {}", authorization);
request = request.header("Authorization", authorization);
request = request.header("Accept", "image/png, image/gif");
} catch (Exception e) {
throw new KeycloakClientException("Cannot construct the request object correctly", e);
}
GXInboundResponse response;
try {
response = request.get();
} catch (Exception e) {
throw new KeycloakClientException("Cannot send request correctly", e);
}
if (response.isSuccessResponse()) {
return response.getStreamedContent();
} else {
throw KeycloakClientException.create("Unable to get avatar image data", response.getHTTPCode(),
response.getHeaderFields()
.getOrDefault("content-type", Collections.singletonList("unknown/unknown")).get(0),
response.getMessage());
}
} catch (IOException e) {
throw new KeycloakClientException("Error getting user's avatar data", e);
}
}
}

View File

@ -2,7 +2,10 @@ package org.gcube.common.keycloak;
import java.net.URL;
import java.util.List;
import java.util.Map;
import org.gcube.common.keycloak.model.JSONWebKeySet;
import org.gcube.common.keycloak.model.PublishedRealmRepresentation;
import org.gcube.common.keycloak.model.TokenIntrospectionResponse;
import org.gcube.common.keycloak.model.TokenResponse;
@ -11,10 +14,14 @@ public interface KeycloakClient {
public static final String PROD_ROOT_SCOPE = "/d4science.research-infrastructures.eu";
public static final String OPEN_ID_URI_PATH = "protocol/openid-connect";
public static final String TOKEN_URI_PATH = "token";
public static final String JWK_URI_PATH = "certs";
public static final String TOKEN_INTROSPECT_URI_PATH = "introspect";
public static final String AVATAR_URI_PATH = "account-avatar";
public final static String D4S_CONTEXT_HEADER_NAME = "X-D4Science-Context";
public static String DEFAULT_REALM = "d4science";
/**
* Returns the Keycloak base {@link URL} for the given context and the default realm (<code>d4science</code>)
*
@ -23,7 +30,6 @@ public interface KeycloakClient {
* @throws KeycloakClientException if something goes wrong discovering the endpoint URL
*/
URL getRealmBaseURL(String context) throws KeycloakClientException;
/**
* Returns the Keycloak base {@link URL} for the given context and in the given realm.
@ -44,6 +50,15 @@ public interface KeycloakClient {
*/
URL getTokenEndpointURL(URL realmBaseURL) throws KeycloakClientException;
/**
* Constructs the Keycloak <code>JWK</code> endpoint {@link URL} from the realm's base URL.
*
* @param realmBaseURL the realm's base URL to use
* @return the Keycloak <code>JWK</code> endpoint URL
* @throws KeycloakClientException if something goes wrong discovering the endpoint URL
*/
URL getJWKEndpointURL(URL realmBaseURL) throws KeycloakClientException;
/**
* Constructs the Keycloak <code>introspection</code> endpoint {@link URL} from the realm's base URL.
*
@ -62,6 +77,27 @@ public interface KeycloakClient {
*/
URL computeIntrospectionEndpointURL(URL tokenEndpointURL) throws KeycloakClientException;
/**
* Constructs the Keycloak <code>avatar</code> endpoint {@link URL} from the realm's base URL.
*
* @param realmBaseURL the realm's base URL to use
* @return the Keycloak <code>avatar</code> endpoint URL
* @throws KeycloakClientException if something goes wrong discovering the endpoint URL
*/
URL getAvatarEndpointURL(URL realmBaseURL) throws KeycloakClientException;
/**
* Gets the realm info setup (RSA <code>public_key</code>, <code>token-service</code> URL,
* <code>account-service</code> URL and <code>tokens-not-before</code> setting)
*
* @param realmURL the realm URL
* @return the configured realm info
* @throws KeycloakClientException if something goes wrong getting realm info
*/
PublishedRealmRepresentation getRealmInfo(URL realmURL) throws KeycloakClientException;
JSONWebKeySet getRealmJSONWebKeySet(URL jwkURL) throws KeycloakClientException;
/**
* Queries an OIDC token from the context's Keycloak server, by using provided clientId and client secret.
*
@ -74,45 +110,17 @@ public interface KeycloakClient {
TokenResponse queryOIDCToken(String context, String clientId, String clientSecret) throws KeycloakClientException;
/**
* Queries an OIDC token from the context's Keycloak server, by using provided clientId and client secret, reducing the audience to the requested one.
* Queries an OIDC token from the context's Keycloak server, by using provided clientId and client secret.
*
* @param context the context where the Keycloak's is needed (e.g. <code>/gcube</code> for DEV)
* @param clientId the client id
* @param clientSecret the client secret
* @param audience an optional parameter to shrink the token's audience to the requested one (e.g. a specific context), by leveraging on the custom HTTP header and corresponding mapper on Keycloak
* @param extraHeaders extra HTTP headers to add to the request
* @return the issued token as {@link TokenResponse} object
* @throws KeycloakClientException if something goes wrong performing the query
*/
TokenResponse queryOIDCTokenWithContext(String context, String clientId, String clientSecret, String audience) throws KeycloakClientException;
TokenResponse queryOIDCToken(String context, String clientId, String clientSecret, Map<String, String> extraHeaders) throws KeycloakClientException;
/**
* Queries an OIDC token for a specific user from the context's Keycloak server, by using provided clientId and client secret and user's username and password.
*
* @param context the context where the Keycloak's is needed (e.g. <code>/gcube</code> for DEV)
* @param clientId the client id
* @param clientSecret the client secret
* @param username the user's username
* @param password the user's password
* @return the issued token as {@link TokenResponse} object
* @throws KeycloakClientException if something goes wrong performing the query
*/
TokenResponse queryOIDCTokenOfUser(String context, String clientId, String clientSecret, String username, String password) throws KeycloakClientException;
/**
* Queries an OIDC token for a specific user from the Keycloak server, by using provided clientId and client secret and user's username and password, reducing the audience to the requested one.
*
* @param tokenURL the token endpoint {@link URL} of the Keycloak server
* @param clientId the client id
* @param clientSecret the client secret
* @param username the user's username
* @param password the user's password
* @param audience an optional parameter to shrink the token's audience to the requested one (e.g. a specific context), by leveraging on the custom HTTP header and corresponding mapper on Keycloak
* @return the issued token as {@link TokenResponse} object
* @throws KeycloakClientException if something goes wrong performing the query
*/
TokenResponse queryOIDCTokenOfUserWithContext(String context, String clientId, String clientSecret, String username, String password, String audience) throws KeycloakClientException;
/**
* Queries an OIDC token from the Keycloak server, by using provided clientId and client secret.
*
@ -125,35 +133,22 @@ public interface KeycloakClient {
TokenResponse queryOIDCToken(URL tokenURL, String clientId, String clientSecret) throws KeycloakClientException;
/**
* Queries an OIDC token from the Keycloak server, by using provided clientId and client secret, reducing the audience to the requested one.
* Queries an OIDC token from the Keycloak server, by using provided clientId and client secret.
* Optionally extra HTTP headers can be provided to be used in the call.
*
* @param tokenURL the token endpoint {@link URL} of the Keycloak server
* @param clientId the client id
* @param clientSecret the client secret
* @param audience an optional parameter to shrink the token's audience to the requested one (e.g. a specific context), by leveraging on the custom HTTP header and corresponding mapper on Keycloak
* @param extraHeaders extra HTTP headers to add to the request
* @return the issued token as {@link TokenResponse} object
* @throws KeycloakClientException if something goes wrong performing the query
*/
TokenResponse queryOIDCTokenWithContext(URL tokenURL, String clientId, String clientSecret, String audience) throws KeycloakClientException;
/**
* Queries an OIDC token for a specific user from the context's Keycloak server, by using provided clientId and client secret and user's username and password, , reducing the audience to the requested one.
*
* @param tokenURL the token endpoint {@link URL} of the Keycloak server
* @param clientId the client id
* @param clientSecret the client secret
* @param username the user's username
* @param password the user's password
* @return the issued token as {@link TokenResponse} object
* @throws KeycloakClientException if something goes wrong performing the query
*/
TokenResponse queryOIDCTokenOfUserWithContext(URL tokenURL, String clientId, String clientSecret, String username, String password, String audience) throws KeycloakClientException;
TokenResponse queryOIDCToken(URL tokenURL, String clientId, String clientSecret, Map<String, String> extraHeaders) throws KeycloakClientException;
/**
* Queries an OIDC token from the Keycloak server, by using provided authorization.
*
* @param context the context where the Keycloak's is needed (e.g. <code>/gcube</code> for DEV)
* @param tokenUrl the token endpoint {@link URL} of the OIDC server
* @param authorization the authorization to be set as header (e.g. a "Basic ...." auth or an encoded JWT access token preceded by the "Bearer " string)
* @return the issued token as {@link TokenResponse} object
* @throws KeycloakClientException if something goes wrong performing the query
@ -161,55 +156,189 @@ public interface KeycloakClient {
TokenResponse queryOIDCToken(String context, String authorization) throws KeycloakClientException;
/**
* Queries an OIDC token from the Keycloak server, by using provided authorization, reducing the audience to the requested one.
*
* @param context the context where the Keycloak's is needed (e.g. <code>/gcube</code> for DEV)
* @param tokenUrl the token endpoint {@link URL} of the OIDC server
* @param authorization the authorization to be set as header (e.g. a "Basic ...." auth or an encoded JWT access token preceded by the "Bearer " string)
* @param audience an optional parameter to shrink the token's audience to the requested one (e.g. a specific context), by leveraging on the custom HTTP header and corresponding mapper on Keycloak
* @return the issued token as {@link TokenResponse} object
* @throws KeycloakClientException if something goes wrong performing the query
*/
TokenResponse queryOIDCTokenWithContext(String context, String authorization, String audience) throws KeycloakClientException;
/**
* Queries an OIDC token for a specific user from the context's Keycloak server, by using provided clientId and client secret and user's username and password.
* Queries an OIDC token from the Keycloak server, by using provided authorization.
* Optionally extra HTTP headers can be provided to be used in the call.
*
* @param context the context where the Keycloak's is needed (e.g. <code>/gcube</code> for DEV)
* @param authorization the authorization to be set as header (e.g. a "Basic ...." auth or an encoded JWT access token preceded by the "Bearer " string)
* @param username the user's username
* @param password the user's password
* @param audience an optional parameter to shrink the token's audience to the requested one (e.g. a specific context), by leveraging on the custom HTTP header and corresponding mapper on Keycloak
* @param extraHeaders extra HTTP headers to add to the request
* @return the issued token as {@link TokenResponse} object
* @throws KeycloakClientException if something goes wrong performing the query
*/
TokenResponse queryOIDCTokenOfUserWithContext(String context, String authorization, String username, String password, String audience) throws KeycloakClientException;
TokenResponse queryOIDCToken(String context, String authorization, Map<String, String> extraHeaders) throws KeycloakClientException;
/**
* Queries an OIDC token from the Keycloak server, by using provided authorization.
*
* @param tokenUrl the token endpoint {@link URL} of the OIDC server
* @param tokenURL the token endpoint {@link URL} of the OIDC server
* @param authorization the authorization to be set as header (e.g. a "Basic ...." auth or an encoded JWT access token preceded by the "Bearer " string)
* @return the issued token as {@link TokenResponse} object
* @throws KeycloakClientException if something goes wrong performing the query
*/
TokenResponse queryOIDCToken(URL tokenURL, String authorization) throws KeycloakClientException;
/**
* Queries an OIDC token from the Keycloak server, by using provided authorization.
* Optionally extra HTTP headers can be provided to be used in the call.
*
* @param tokenURL the token endpoint {@link URL} of the OIDC server
* @param authorization the authorization to be set as header (e.g. a "Basic ...." auth or an encoded JWT access token preceded by the "Bearer " string)
* @param extraHeaders extra HTTP headers to add to the request
* @return the issued token as {@link TokenResponse} object
* @throws KeycloakClientException if something goes wrong performing the query
*/
TokenResponse queryOIDCToken(URL tokenURL, String authorization, Map<String, String> extraHeaders) throws KeycloakClientException;
/**
* Queries an OIDC token from the context's Keycloak server, by using provided clientId and client secret, reducing the audience to the requested one.
*
* The implementation uses the custom <code>X-D4Science-Context</code> HTTP header that the proper mapper on Keycloak uses to reduce the audience
*
* @param context the context where the Keycloak's is needed (e.g. <code>/gcube</code> for DEV)
* @param clientId the client id
* @param clientSecret the client secret
* @param audience an optional parameter to shrink the token's audience to the requested one (e.g. a specific context), by leveraging on the custom HTTP header and corresponding mapper on Keycloak
* @return the issued token as {@link TokenResponse} object
* @throws KeycloakClientException if something goes wrong performing the query
*/
TokenResponse queryOIDCTokenWithContext(String context, String clientId, String clientSecret, String audience)
throws KeycloakClientException;
/**
* Queries an OIDC token from the context's Keycloak server, by using provided clientId and client secret, reducing the audience to the requested one.
* Optionally extra HTTP headers can be provided to be used in the call.
*
* The implementation uses the custom <code>X-D4Science-Context</code> HTTP header that the proper mapper on Keycloak uses to reduce the audience
*
* @param context the context where the Keycloak's is needed (e.g. <code>/gcube</code> for DEV)
* @param clientId the client id
* @param clientSecret the client secret
* @param audience an optional parameter to shrink the token's audience to the requested one (e.g. a specific context), by leveraging on the custom HTTP header and corresponding mapper on Keycloak
* @param extraHeaders extra HTTP headers to add to the request
* @return the issued token as {@link TokenResponse} object
* @throws KeycloakClientException if something goes wrong performing the query
*/
TokenResponse queryOIDCTokenWithContext(String context, String clientId, String clientSecret, String audience, Map<String, String> extraHeaders)
throws KeycloakClientException;
/**
* Queries an OIDC token from the Keycloak server, by using provided clientId and client secret, reducing the audience to the requested one.
*
* The implementation uses the custom <code>X-D4Science-Context</code> HTTP header that the proper mapper on Keycloak uses to reduce the audience
*
* @param tokenURL the token endpoint {@link URL} of the Keycloak server
* @param clientId the client id
* @param clientSecret the client secret
* @param audience an optional parameter to shrink the token's audience to the requested one (e.g. a specific context), by leveraging on the custom HTTP header and corresponding mapper on Keycloak
* @return the issued token as {@link TokenResponse} object
* @throws KeycloakClientException if something goes wrong performing the query
*/
TokenResponse queryOIDCTokenWithContext(URL tokenURL, String clientId, String clientSecret, String audience)
throws KeycloakClientException;
/**
* Queries an OIDC token from the Keycloak server, by using provided clientId and client secret, reducing the audience to the requested one.
* Optionally extra HTTP headers can be provided to be used in the call.
*
* The implementation uses the custom <code>X-D4Science-Context</code> HTTP header that the proper mapper on Keycloak uses to reduce the audience
*
* @param tokenURL the token endpoint {@link URL} of the Keycloak server
* @param clientId the client id
* @param clientSecret the client secret
* @param audience an optional parameter to shrink the token's audience to the requested one (e.g. a specific context), by leveraging on the custom HTTP header and corresponding mapper on Keycloak
* @param extraHeaders extra HTTP headers to add to the request
* @return the issued token as {@link TokenResponse} object
* @throws KeycloakClientException if something goes wrong performing the query
*/
TokenResponse queryOIDCTokenWithContext(URL tokenURL, String clientId, String clientSecret, String audience, Map<String, String> extraHeaders)
throws KeycloakClientException;
/**
* Queries an OIDC token from the Keycloak server, by using provided authorization, reducing the audience to the requested one.
*
* @param tokenUrl the token endpoint {@link URL} of the OIDC server
* @param context the context where the Keycloak's is needed (e.g. <code>/gcube</code> for DEV)
* @param authorization the authorization to be set as header (e.g. a "Basic ...." auth or an encoded JWT access token preceded by the "Bearer " string)
* @param audience an optional parameter to shrink the token's audience to the requested one (e.g. a specific context), by leveraging on the custom HTTP header and corresponding mapper on Keycloak
* @return the issued token as {@link TokenResponse} object
* @throws KeycloakClientException if something goes wrong performing the query
*/
TokenResponse queryOIDCTokenWithContext(URL tokenURL, String authorization, String audience) throws KeycloakClientException;
TokenResponse queryOIDCTokenWithContext(String context, String authorization, String audience)
throws KeycloakClientException;
/**
* Queries an OIDC token from the Keycloak server, by using provided authorization, reducing the audience to the requested one.
* Optionally extra HTTP headers can be provided to be used in the call.
*
* @param context the context where the Keycloak's is needed (e.g. <code>/gcube</code> for DEV)
* @param authorization the authorization to be set as header (e.g. a "Basic ...." auth or an encoded JWT access token preceded by the "Bearer " string)
* @param audience an optional parameter to shrink the token's audience to the requested one (e.g. a specific context), by leveraging on the custom HTTP header and corresponding mapper on Keycloak
* @param extraHeaders extra HTTP headers to add to the request
* @return the issued token as {@link TokenResponse} object
* @throws KeycloakClientException if something goes wrong performing the query
*/
TokenResponse queryOIDCTokenWithContext(String context, String authorization, String audience, Map<String, String> extraHeaders)
throws KeycloakClientException;
/**
* Queries an OIDC token from the Keycloak server, by using provided authorization, reducing the audience to the requested one.
*
* @param tokenURL the token endpoint {@link URL} of the OIDC server
* @param authorization the authorization to be set as header (e.g. a "Basic ...." auth or an encoded JWT access token preceded by the "Bearer " string)
* @param audience an optional parameter to shrink the token's audience to the requested one (e.g. a specific context), by leveraging on the custom HTTP header and corresponding mapper on Keycloak
* @return the issued token as {@link TokenResponse} object
* @throws KeycloakClientException if something goes wrong performing the query
*/
TokenResponse queryOIDCTokenWithContext(URL tokenURL, String authorization, String audience)
throws KeycloakClientException;
/**
* Queries an OIDC token from the Keycloak server, by using provided authorization, reducing the audience to the requested one.
* Optionally extra HTTP headers can be provided to be used in the call.
*
* @param tokenURL the token endpoint {@link URL} of the OIDC server
* @param authorization the authorization to be set as header (e.g. a "Basic ...." auth or an encoded JWT access token preceded by the "Bearer " string)
* @param audience an optional parameter to shrink the token's audience to the requested one (e.g. a specific context), by leveraging on the custom HTTP header and corresponding mapper on Keycloak
* @param extraHeaders extra HTTP headers to add to the request
* @return the issued token as {@link TokenResponse} object
* @throws KeycloakClientException if something goes wrong performing the query
*/
TokenResponse queryOIDCTokenWithContext(URL tokenURL, String authorization, String audience, Map<String, String> extraHeaders)
throws KeycloakClientException;
/**
* Queries an OIDC token for a specific user from the context's Keycloak server, by using provided clientId and client secret and user's username and password.
*
* @param tokenUrl the token endpoint {@link URL} of the OIDC server
* @param context the context where the Keycloak's is needed (e.g. <code>/gcube</code> for DEV)
* @param clientId the client id
* @param clientSecret the client secret
* @param username the user's username
* @param password the user's password
* @return the issued token as {@link TokenResponse} object
* @throws KeycloakClientException if something goes wrong performing the query
*/
TokenResponse queryOIDCTokenOfUser(String context, String clientId, String clientSecret, String username,
String password) throws KeycloakClientException;
/**
* Queries an OIDC token for a specific user from the context's Keycloak server, by using provided clientId and client secret and user's username and password.
* Optionally extra HTTP headers can be provided to be used in the call.
*
* @param context the context where the Keycloak's is needed (e.g. <code>/gcube</code> for DEV)
* @param clientId the client id
* @param clientSecret the client secret
* @param username the user's username
* @param password the user's password
* @param extraHeaders extra HTTP headers to add to the request
* @return the issued token as {@link TokenResponse} object
* @throws KeycloakClientException if something goes wrong performing the query
*/
TokenResponse queryOIDCTokenOfUser(String context, String clientId, String clientSecret, String username,
String password, Map<String, String> extraHeaders) throws KeycloakClientException;
/**
* Queries an OIDC token for a specific user from the context's Keycloak server, by using provided clientId and client secret and user's username and password.
*
* @param context the context where the Keycloak's is needed (e.g. <code>/gcube</code> for DEV)
* @param authorization the authorization to be set as header (e.g. a "Basic ...." auth or an encoded JWT access token preceded by the "Bearer " string)
* @param username the user's username
* @param password the user's password
@ -217,7 +346,124 @@ public interface KeycloakClient {
* @return the issued token as {@link TokenResponse} object
* @throws KeycloakClientException if something goes wrong performing the query
*/
TokenResponse queryOIDCTokenOfUserWithContext(URL tokenURL, String authorization, String username, String password, String audience) throws KeycloakClientException;
TokenResponse queryOIDCTokenOfUserWithContext(String context, String authorization, String username,
String password, String audience) throws KeycloakClientException;
/**
* Queries an OIDC token for a specific user from the Keycloak server, by using provided clientId and client secret and user's username and password, reducing the audience to the requested one.
*
* The implementation uses the custom <code>X-D4Science-Context</code> HTTP header that the proper mapper on Keycloak uses to reduce the audience
*
* @param context the context where the Keycloak's is needed (e.g. <code>/gcube</code> for DEV)
* @param clientId the client id
* @param clientSecret the client secret
* @param username the user's username
* @param password the user's password
* @param audience an optional parameter to shrink the token's audience to the requested one (e.g. a specific context), by leveraging on the custom HTTP header and corresponding mapper on Keycloak
* @return the issued token as {@link TokenResponse} object
* @throws KeycloakClientException if something goes wrong performing the query
*/
TokenResponse queryOIDCTokenOfUserWithContext(String context, String clientId, String clientSecret, String username,
String password, String audience) throws KeycloakClientException;
/**
* Queries an OIDC token for a specific user from the Keycloak server, by using provided clientId and client secret and user's username and password, reducing the audience to the requested one.
* Optionally extra HTTP headers can be provided to be used in the call.
*
* The implementation uses the custom <code>X-D4Science-Context</code> HTTP header that the proper mapper on Keycloak uses to reduce the audience
*
* @param context the context where the Keycloak's is needed (e.g. <code>/gcube</code> for DEV)
* @param clientId the client id
* @param clientSecret the client secret
* @param username the user's username
* @param password the user's password
* @param audience an optional parameter to shrink the token's audience to the requested one (e.g. a specific context), by leveraging on the custom HTTP header and corresponding mapper on Keycloak
* @param extraHeaders extra HTTP headers to add to the request
* @return the issued token as {@link TokenResponse} object
* @throws KeycloakClientException if something goes wrong performing the query
*/
TokenResponse queryOIDCTokenOfUserWithContext(String context, String clientId, String clientSecret,
String username, String password, String audience, Map<String, String> extraHeaders) throws KeycloakClientException;
/**
* Queries an OIDC token for a specific user from the context's Keycloak server, by using provided clientId and client secret and user's username and password, reducing the audience to the requested one.
*
* The implementation uses the custom <code>X-D4Science-Context</code> HTTP header that the proper mapper on Keycloak uses to reduce the audience
*
* @param tokenURL the token endpoint {@link URL} of the Keycloak server
* @param clientId the client id
* @param clientSecret the client secret
* @param username the user's username
* @param password the user's password
* @param audience an optional parameter to shrink the token's audience to the requested one (e.g. a specific context), by leveraging on the custom HTTP header and corresponding mapper on Keycloak
* @return the issued token as {@link TokenResponse} object
* @throws KeycloakClientException if something goes wrong performing the query
*/
TokenResponse queryOIDCTokenOfUserWithContext(URL tokenURL, String clientId, String clientSecret, String username,
String password, String audience) throws KeycloakClientException;
/**
* Queries an OIDC token for a specific user from the context's Keycloak server, by using provided clientId and client secret and user's username and password, , reducing the audience to the requested one.
* Optionally extra HTTP headers can be provided to be used in the call.
*
* @param tokenURL the token endpoint {@link URL} of the Keycloak server
* @param clientId the client id
* @param clientSecret the client secret
* @param username the user's username
* @param password the user's password
* @param audience an optional parameter to shrink the token's audience to the requested one (e.g. a specific context), by leveraging on the custom HTTP header and corresponding mapper on Keycloak
* @param extraHeaders extra HTTP headers to add to the request
* @return the issued token as {@link TokenResponse} object
* @throws KeycloakClientException if something goes wrong performing the query
*/
TokenResponse queryOIDCTokenOfUserWithContext(URL tokenURL, String clientId, String clientSecret,
String username, String password, String audience, Map<String, String> extraHeaders) throws KeycloakClientException;
/**
* Queries an OIDC token for a specific user from the context's Keycloak server, by using provided clientId and client secret and user's username and password.
* Optionally extra HTTP headers can be provided to be used in the call.
*
* @param context the context where the Keycloak's is needed (e.g. <code>/gcube</code> for DEV)
* @param authorization the authorization to be set as header (e.g. a "Basic ...." auth or an encoded JWT access token preceded by the "Bearer " string)
* @param username the user's username
* @param password the user's password
* @param audience an optional parameter to shrink the token's audience to the requested one (e.g. a specific context), by leveraging on the custom HTTP header and corresponding mapper on Keycloak
* @param extraHeaders extra HTTP headers to add to the request
* @return the issued token as {@link TokenResponse} object
* @throws KeycloakClientException if something goes wrong performing the query
*/
TokenResponse queryOIDCTokenOfUserWithContext(String context, String authorization, String username,
String password, String audience, Map<String, String> extraHeaders) throws KeycloakClientException;
/**
* Queries an OIDC token for a specific user from the context's Keycloak server, by using provided clientId and client secret and user's username and password.
*
* @param tokenURL the token endpoint {@link URL} of the OIDC server
* @param authorization the authorization to be set as header (e.g. a "Basic ...." auth or an encoded JWT access token preceded by the "Bearer " string)
* @param username the user's username
* @param password the user's password
* @param audience an optional parameter to shrink the token's audience to the requested one (e.g. a specific context), by leveraging on the custom HTTP header and corresponding mapper on Keycloak
* @return the issued token as {@link TokenResponse} object
* @throws KeycloakClientException if something goes wrong performing the query
*/
TokenResponse queryOIDCTokenOfUserWithContext(URL tokenURL, String authorization, String username, String password,
String audience) throws KeycloakClientException;
/**
* Queries an OIDC token for a specific user from the context's Keycloak server, by using provided clientId and client secret and user's username and password.
* Optionally extra HTTP headers can be provided to be used in the call.
*
* @param tokenURL the token endpoint {@link URL} of the OIDC server
* @param authorization the authorization to be set as header (e.g. a "Basic ...." auth or an encoded JWT access token preceded by the "Bearer " string)
* @param username the user's username
* @param password the user's password
* @param audience an optional parameter to shrink the token's audience to the requested one (e.g. a specific context), by leveraging on the custom HTTP header and corresponding mapper on Keycloak
* @param extraHeaders extra HTTP headers to add to the request
* @return the issued token as {@link TokenResponse} object
* @throws KeycloakClientException if something goes wrong performing the query
*/
TokenResponse queryOIDCTokenOfUserWithContext(URL tokenURL, String authorization, String username,
String password, String audience, Map<String, String> extraHeaders) throws KeycloakClientException;
/**
* Queries an UMA token from the Keycloak server, by using provided authorization, for the given audience (context),
@ -237,7 +483,7 @@ public interface KeycloakClient {
* Queries an UMA token from the Keycloak server, by using provided authorization, for the given audience (context),
* in URLEncoded form or not, and optionally a list of permissions.
*
* @param tokenUrl the token endpoint {@link URL} of the OIDC server
* @param tokenURL the token endpoint {@link URL} of the OIDC server
* @param authorization the authorization to be set as header (e.g. a "Basic ...." auth or an encoded JWT access token preceded by the "Bearer " string)
* @param audience the audience (context) where to request the issuing of the ticket (URLEncoded)
* @param permissions a list of permissions, can be <code>null</code>
@ -252,7 +498,7 @@ public interface KeycloakClient {
* for the given audience (context), in URLEncoded form or not, and optionally a list of permissions.
*
* @param context the context where the Keycloak's is needed (e.g. <code>/gcube</code> for DEV)
* @param tokenResponse the previously issued token as {@link TokenResponse} object
* @param oidcTokenResponse the previously issued token as {@link TokenResponse} object
* @param audience the audience (context) where to request the issuing of the ticket
* @param permissions a list of permissions, can be <code>null</code>
* @return the issued token as {@link TokenResponse} object
@ -265,8 +511,8 @@ public interface KeycloakClient {
* Queries an UMA token from the Keycloak server, by using access-token provided by the {@link TokenResponse} object
* for the given audience (context), in URLEncoded form or not, and optionally a list of permissions.
*
* @param tokenUrl the token endpoint {@link URL} of the OIDC server
* @param tokenResponse the previously issued token as {@link TokenResponse} object
* @param tokenURL the token endpoint {@link URL} of the OIDC server
* @param oidcTokenResponse the previously issued token as {@link TokenResponse} object
* @param audience the audience (context) where to request the issuing of the ticket
* @param permissions a list of permissions, can be <code>null</code>
* @return the issued token as {@link TokenResponse} object
@ -326,7 +572,7 @@ public interface KeycloakClient {
* Client id will be read from "issued for" access token's claim and client secret will be not sent.
* <br><b>NOTE</b>: For <code>public</code> clients types only.
*
* @param tokenUrl the token endpoint {@link URL} of the OIDC server
* @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
@ -351,7 +597,7 @@ public interface KeycloakClient {
* 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 tokenUrl the token endpoint {@link URL} of the OIDC server
* @param tokenURL the token endpoint {@link URL} of the OIDC server
* @param clientId the requestor client id, may be <code>null</code> and in this case will be take from the access token "issued for" claim
* @param clientSecret the requestor client secret, may be <code>null</code> for non-confidential clients
* @param tokenResponse the previously issued token as {@link TokenResponse} object
@ -379,7 +625,7 @@ public interface KeycloakClient {
* 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 tokenURL the token endpoint {@link URL} of the OIDC server
* @param clientId the requestor client id
* @param clientSecret the requestor client secret, may be <code>null</code> for non-confidential clients
* @param refreshTokenJWTString the previously issued refresh token JWT string
@ -389,6 +635,94 @@ public interface KeycloakClient {
TokenResponse refreshToken(URL tokenURL, String clientId, String clientSecret, String refreshTokenJWTString)
throws KeycloakClientException;
/**
* Exchanges a token for another access token for a specific client and a specific audience
*
* @param context the context where the Keycloak's is needed (e.g. <code>/gcube</code> for DEV)
* @param oidcAccessToken the original access token to exchange
* @param clientId the authorized client's id
* @param clientSecret the authorized client's secret
* @param audience the requested token audience
* @return the exchanged token response
* @throws KeycloakClientException if an error occurs during the exchange
*/
TokenResponse exchangeTokenForAccessToken(String context, String oidcAccessToken, String clientId,
String clientSecret, String audience) throws KeycloakClientException;
/**
* Exchanges a token for another access token for a specific client and a specific audience
*
* @param tokenURL the token endpoint URL
* @param oidcAccessToken the original access token to exchange
* @param clientId the authorized client's id
* @param clientSecret the authorized client's secret
* @param audience the requested token audience
* @return the exchanged token response
* @throws KeycloakClientException if an error occurs during the exchange
*/
TokenResponse exchangeTokenForAccessToken(URL tokenURL, String oidcAccessToken, String clientId,
String clientSecret, String audience) throws KeycloakClientException;
/**
* Exchanges a token for another access and a refresh tokens for a specific client and a specific audience
*
* @param context the context where the Keycloak's is needed (e.g. <code>/gcube</code> for DEV)
* @param oidcAccessToken the original access token to exchange
* @param clientId the authorized client's id
* @param clientSecret the authorized client's secret
* @param audience the requested token audience
* @return the exchanged token response
* @throws KeycloakClientException if an error occurs during the exchange
*/
TokenResponse exchangeTokenForRefreshToken(String context, String oidcAccessToken, String clientId,
String clientSecret, String audience) throws KeycloakClientException;
/**
* Exchanges a token for another access and a refresh tokens for a specific client and a specific audience
*
* @param tokenURL the token endpoint URL
* @param oidcAccessToken the original access token to exchange
* @param clientId the authorized client's id
* @param clientSecret the authorized client's secret
* @param audience the requested token audience
* @return the exchanged token response
* @throws KeycloakClientException if an error occurs during the exchange
*/
TokenResponse exchangeTokenForRefreshToken(URL tokenURL, String oidcAccessToken, String clientId,
String clientSecret, String audience) throws KeycloakClientException;
/**
* Exchanges a token for another access and an offline refresh tokens for a specific client and a specific audience
* The refresh token will be of the offline type only if the original token has the <code>offline_access</code> within its scopes
*
* @param tokenURL the token endpoint URL
* @param oidcAccessToken the original access token to exchange
* @param clientId the authorized client's id
* @param clientSecret the authorized client's secret
* @param audience the requested token audience
* @return the exchanged token response
* @throws IllegalArgumentException if the original token does'nt contains the <code>offline_access</code> scope within its scopes or if is impossible to parse the access token as JSON
* @throws KeycloakClientException if an error occurs during the exchange
*/
TokenResponse exchangeTokenForOfflineToken(String context, String oidcAccessToken, String clientId,
String clientSecret, String audience) throws IllegalArgumentException, KeycloakClientException;
/**
* Exchanges a token for another access and an offline refresh tokens for a specific client and a specific audience
* The refresh token will be of the offline type only if the original token has the scope <code>offline_access</code> within its scopes
*
* @param tokenURL the token endpoint URL
* @param oidcAccessToken the original access token to exchange
* @param clientId the authorized client's id
* @param clientSecret the authorized client's secret
* @param audience the requested token audience
* @return the exchanged token response
* @throws IllegalArgumentException if the original token does'nt contains the <code>offline_access</code> scope within its scopes or if is impossible to parse the access token as JSON
* @throws KeycloakClientException if an error occurs during the exchange
*/
TokenResponse exchangeTokenForOfflineToken(URL tokenURL, String oidcAccessToken, String clientId,
String clientSecret, String audience) throws IllegalArgumentException, KeycloakClientException;
/**
* Introspects an access token against the Keycloak server.
*
@ -441,4 +775,8 @@ public interface KeycloakClient {
boolean isAccessTokenVerified(URL introspectionURL, String clientId, String clientSecret,
String accessTokenJWTString) throws KeycloakClientException;
byte[] getAvatarData(String context, TokenResponse tokenResponse) throws KeycloakClientException;
byte[] getAvatarData(URL avatarURL, TokenResponse tokenResponse) throws KeycloakClientException;
}

View File

@ -7,9 +7,32 @@ public class KeycloakClientFactory {
protected static final Logger logger = LoggerFactory.getLogger(KeycloakClientFactory.class);
protected static String CUSTOM_BASE_URL;
public static void setCustomBaseURL(String customBaseURL) {
if (customBaseURL != null) {
logger.info("Setting custom base URL static value to {}", customBaseURL);
} else {
logger.info("Removing custom base URL static value");
}
if (customBaseURL == null || customBaseURL.endsWith("/")) {
CUSTOM_BASE_URL = customBaseURL;
} else {
CUSTOM_BASE_URL = customBaseURL += "/";
}
}
public static String getCustomBaseURL() {
return CUSTOM_BASE_URL;
}
public static KeycloakClient newInstance() {
logger.debug("Instantiating a new keycloak client instance");
return new DefaultKeycloakClient();
DefaultKeycloakClient newInstance = new DefaultKeycloakClient();
if (getCustomBaseURL() != null) {
newInstance.setCustomBaseURL(CUSTOM_BASE_URL);
}
return newInstance;
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gcube.common.keycloak.model;
import java.io.Serializable;
@ -10,6 +26,9 @@ import java.util.Set;
import org.gcube.com.fasterxml.jackson.annotation.JsonIgnore;
import org.gcube.com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
*/
public class AccessToken extends IDToken {
private static final long serialVersionUID = 6364784008775737335L;
@ -156,4 +175,12 @@ public class AccessToken extends IDToken {
this.trustedCertificates = trustedCertificates;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
}

View File

@ -1,7 +1,26 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gcube.common.keycloak.model;
import org.gcube.com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
*/
public class AddressClaimSet {
public static final String FORMATTED = "formatted";

View File

@ -1,7 +1,26 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gcube.common.keycloak.model;
import org.gcube.com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
*/
public class IDToken extends JsonWebToken {
private static final long serialVersionUID = 8406175387651749097L;

View File

@ -0,0 +1,38 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gcube.common.keycloak.model;
import org.gcube.com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JSONWebKeySet {
@JsonProperty("keys")
private JWK[] keys;
public JWK[] getKeys() {
return keys;
}
public void setKeys(JWK[] keys) {
this.keys = keys;
}
}

View File

@ -0,0 +1,112 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gcube.common.keycloak.model;
import java.util.HashMap;
import java.util.Map;
import org.gcube.com.fasterxml.jackson.annotation.JsonAnyGetter;
import org.gcube.com.fasterxml.jackson.annotation.JsonAnySetter;
import org.gcube.com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JWK {
public static final String KEY_ID = "kid";
public static final String KEY_TYPE = "kty";
public static final String ALGORITHM = "alg";
public static final String PUBLIC_KEY_USE = "use";
public enum Use {
SIG("sig"),
ENCRYPTION("enc");
private String str;
Use(String str) {
this.str = str;
}
public String asString() {
return str;
}
}
@JsonProperty(KEY_ID)
private String keyId;
@JsonProperty(KEY_TYPE)
private String keyType;
@JsonProperty(ALGORITHM)
private String algorithm;
@JsonProperty(PUBLIC_KEY_USE)
private String publicKeyUse;
protected Map<String, Object> otherClaims = new HashMap<String, Object>();
public String getKeyId() {
return keyId;
}
public void setKeyId(String keyId) {
this.keyId = keyId;
}
public String getKeyType() {
return keyType;
}
public void setKeyType(String keyType) {
this.keyType = keyType;
}
public String getAlgorithm() {
return algorithm;
}
public void setAlgorithm(String algorithm) {
this.algorithm = algorithm;
}
public String getPublicKeyUse() {
return publicKeyUse;
}
public void setPublicKeyUse(String publicKeyUse) {
this.publicKeyUse = publicKeyUse;
}
@JsonAnyGetter
public Map<String, Object> getOtherClaims() {
return otherClaims;
}
@JsonAnySetter
public void setOtherClaims(String name, Object value) {
otherClaims.put(name, value);
}
}

View File

@ -15,6 +15,9 @@ import org.gcube.common.keycloak.model.util.StringOrArrayDeserializer;
import org.gcube.common.keycloak.model.util.StringOrArraySerializer;
import org.gcube.common.keycloak.model.util.Time;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
*/
public class JsonWebToken implements Serializable {
private static final long serialVersionUID = -8136409077130940942L;
@ -80,7 +83,7 @@ public class JsonWebToken implements Serializable {
/**
* Tests that the token is not expired and is not-before.
*
* @return
* @return <code>true</code> if is not expired and is not-before
*/
@JsonIgnore
public boolean isActive() {
@ -98,6 +101,7 @@ public class JsonWebToken implements Serializable {
/**
* Set issuedAt to the current time
* @return the token itself
*/
@JsonIgnore
public JsonWebToken issuedNow() {
@ -183,7 +187,7 @@ public class JsonWebToken implements Serializable {
/**
* OAuth client the token was issued for.
*
* @return
* @return the issued for vale
*/
public String getIssuedFor() {
return issuedFor;
@ -197,7 +201,7 @@ public class JsonWebToken implements Serializable {
/**
* This is a map of any other claims and data that might be in the IDToken. Could be custom claims set up by the auth server
*
* @return
* @return the object's other claims
*/
@JsonAnyGetter
public Map<String, Object> getOtherClaims() {

View File

@ -1,16 +1,36 @@
package org.gcube.common.keycloak.model;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.gcube.com.fasterxml.jackson.annotation.JsonInclude.Include;
import org.gcube.com.fasterxml.jackson.core.JsonProcessingException;
import org.gcube.com.fasterxml.jackson.core.type.TypeReference;
import org.gcube.com.fasterxml.jackson.databind.ObjectMapper;
import org.gcube.com.fasterxml.jackson.databind.ObjectWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.DeserializationException;
import io.jsonwebtoken.io.Deserializer;
/**
* @author <a href="mailto:mauro.mugnaini@nubisware.com">Mauro Mugnaini</a>
*/
public class ModelUtils {
protected static final Logger logger = LoggerFactory.getLogger(ModelUtils.class);
@ -37,6 +57,80 @@ public class ModelUtils {
}
}
/**
* Creates a {@link RSAPublicKey} instance from its string PEM representation
*
* @param publicKeyPem the public key PEM string
* @return the RSA public key
* @throws Exception if it's not possible to create the RSA public key from the PEM string
*/
public static RSAPublicKey createRSAPublicKey(String publicKeyPem) throws Exception {
return (RSAPublicKey) createPublicKey(publicKeyPem, "RSA");
}
/**
* Creates a {@link PublicKey} instance from its string PEM representation
*
* @param publicKeyPem the public key PEM string
* @param algorithm the key type (e.g. RSA)
* @return the public key
* @throws Exception if it's not possible to create the public key from the PEM string
*/
public static PublicKey createPublicKey(String publicKeyPem, String algorithm) throws Exception {
try {
String publicKey = publicKeyPem.replaceFirst("-----BEGIN (.*)-----\n", "");
publicKey = publicKey.replaceFirst("-----END (.*)-----", "");
publicKey = publicKey.replaceAll("\r\n", "");
publicKey = publicKey.replaceAll("\n", "");
byte[] encoded = Base64.getDecoder().decode(publicKey);
KeyFactory kf = KeyFactory.getInstance(algorithm);
return kf.generatePublic(new X509EncodedKeySpec(encoded));
} catch (Exception e) {
throw new RuntimeException("Cannot create public key from PEM string", e);
}
}
/**
* Verifies the token validity
*
* @param token the base64 JWT token string
* @param publicKey the realm's public key on server
* @return <code>true</code> if the token is valid, <code>false</code> otherwise
* @throws Exception if an error occurs constructing the verifier
*/
public static boolean isValid(String token, PublicKey publicKey) throws Exception {
return isValid(token, publicKey, true);
}
/**
* Verifies the token validity
*
* @param token the base64 JWT token string
* @param publicKey the public key to use for verification
* @param checkExpiration if <code>false</code> token expiration check is disabled
* @return <code>true</code> if the token is valid, <code>false</code> otherwise
* @throws Exception if an error occurs constructing the verifier
*/
public static boolean isValid(String token, PublicKey publicKey, boolean checkExpiration) throws Exception {
JwtParser jwtParser = Jwts.parser().json(new GcubeJacksonDeserializer()).verifyWith(publicKey).build();
try {
jwtParser.parse(token);
return true;
} catch (ExpiredJwtException e) {
// This is OK because expiration check is after the signature validation in the implementation
if (logger.isDebugEnabled()) {
logger.debug("JWT is expired: {}", e.getMessage());
}
return !checkExpiration;
} catch (Exception e) {
if (logger.isDebugEnabled()) {
logger.debug("JWT is not verified: {}", e.getMessage());
}
return false;
}
}
public static String getAccessTokenPayloadJSONStringFrom(TokenResponse tokenResponse) throws Exception {
return getAccessTokenPayloadJSONStringFrom(tokenResponse, true);
}
@ -146,4 +240,23 @@ public class ModelUtils {
return "";
}
}
}
public static class GcubeJacksonDeserializer implements Deserializer<Map<String,?>> {
@Override
public Map<String, ?> deserialize(byte[] bytes) throws DeserializationException {
return deserialize(new InputStreamReader(new ByteArrayInputStream(bytes)));
}
@Override
public Map<String, ?> deserialize(Reader reader) throws DeserializationException {
try {
return new ObjectMapper().readValue(reader, new TypeReference<HashMap<String,Object>>() {});
} catch (IOException e) {
throw new DeserializationException("Cannot deserialize JSON with GCube Jackson", e);
}
}
}
}

View File

@ -1,5 +1,8 @@
package org.gcube.common.keycloak.model;
/**
* @author <a href="mailto:mauro.mugnaini@nubisware.com">Mauro Mugnaini</a>
*/
public class OIDCConstants {
public static final String PERMISSION_PARAMETER = "permission";
@ -7,6 +10,7 @@ public class OIDCConstants {
public static final String SCOPE_PARAMETER = "scope";
public static final String CLIENT_CREDENTIALS_GRANT_TYPE = "client_credentials";
public static final String UMA_TOKEN_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:uma-ticket";
public static final String TOKEN_EXCHANGE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange";
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";
@ -16,5 +20,11 @@ public class OIDCConstants {
public static final String PASSWORD_GRANT_TYPE = "password";
public static final String USERNAME_PARAMETER = "username";
public static final String PASSWORD_PARAMETER = "password";
public static final String SUBJECT_TOKEN_PARAMETER = "subject_token";
public static final String SUBJECT_TOKEN_TYPE_PARAMETER = "subject_token_type";
public static final String REQUESTED_TOKEN_TYPE_PARAMETER = "requested_token_type";
public static final String ACCESS_TOKEN_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token";
public static final String REFRESH_TOKEN_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:refresh_token";
public static final String OFFLINE_ACCESS_SCOPE = "offline_access";
}

View File

@ -0,0 +1,107 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gcube.common.keycloak.model;
import java.security.interfaces.RSAPublicKey;
import org.gcube.com.fasterxml.jackson.annotation.JsonIgnore;
import org.gcube.com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @author (modified by) <a href="mailto:mauro.mugnaini@nubisware.com">Mauro Mugnaini</a>
*/
public class PublishedRealmRepresentation {
protected String realm;
@JsonProperty("public_key")
protected String publicKeyPem;
@JsonProperty("token-service")
protected String tokenServiceUrl;
@JsonProperty("account-service")
protected String accountServiceUrl;
@JsonProperty("tokens-not-before")
protected int notBefore;
@JsonIgnore
protected volatile transient RSAPublicKey publicKey;
public String getRealm() {
return realm;
}
public void setRealm(String realm) {
this.realm = realm;
}
public String getPublicKeyPem() {
return publicKeyPem;
}
public void setPublicKeyPem(String publicKeyPem) {
this.publicKeyPem = publicKeyPem;
this.publicKey = null;
}
@JsonIgnore
public RSAPublicKey getPublicKey() {
if (publicKey != null)
return publicKey;
if (publicKeyPem != null) {
try {
publicKey = ModelUtils.createRSAPublicKey(publicKeyPem);
} catch (Exception e) {
e.printStackTrace();
}
}
return publicKey;
}
@JsonIgnore
public void setPublicKey(RSAPublicKey publicKey) {
this.publicKey = publicKey;
// this.publicKeyPem = PemUtils.encodeKey(publicKey);
}
public String getTokenServiceUrl() {
return tokenServiceUrl;
}
public void setTokenServiceUrl(String tokenServiceUrl) {
this.tokenServiceUrl = tokenServiceUrl;
}
public String getAccountServiceUrl() {
return accountServiceUrl;
}
public void setAccountServiceUrl(String accountServiceUrl) {
this.accountServiceUrl = accountServiceUrl;
}
public int getNotBefore() {
return notBefore;
}
public void setNotBefore(int notBefore) {
this.notBefore = notBefore;
}
}

View File

@ -1,5 +1,24 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gcube.common.keycloak.model;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
*/
public class RefreshToken extends AccessToken {
private static final long serialVersionUID = 2646534143077862960L;

View File

@ -1,3 +1,19 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gcube.common.keycloak.model;
import java.util.List;
@ -5,6 +21,9 @@ import java.util.List;
import org.gcube.com.fasterxml.jackson.annotation.JsonProperty;
import org.gcube.common.keycloak.model.idm.authorization.Permission;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class TokenIntrospectionResponse extends JsonWebToken {
private static final long serialVersionUID = -3105799239959636906L;

View File

@ -10,6 +10,9 @@ import org.gcube.com.fasterxml.jackson.annotation.JsonProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author <a href="mailto:mauro.mugnaini@nubisware.com">Mauro Mugnaini</a>
*/
public class TokenResponse implements Serializable {
protected static Logger logger = LoggerFactory.getLogger(TokenResponse.class);

View File

@ -1,3 +1,19 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gcube.common.keycloak.model;
import java.util.HashMap;
@ -295,7 +311,7 @@ public class UserInfo {
/**
* This is a map of any other claims and data that might be in the UserInfo. Could be custom claims set up by the auth server
*
* @return
* @return the object's other claims
*/
@JsonAnyGetter
public Map<String, Object> getOtherClaims() {

View File

@ -1,3 +1,19 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gcube.common.keycloak.model.idm.authorization;
import java.util.HashSet;
@ -9,6 +25,9 @@ import org.gcube.com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.gcube.com.fasterxml.jackson.annotation.JsonInclude;
import org.gcube.com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class Permission {

View File

@ -1,3 +1,19 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gcube.common.keycloak.model.util;
import java.io.IOException;

View File

@ -1,3 +1,19 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gcube.common.keycloak.model.util;
import java.io.IOException;

View File

@ -1,3 +1,19 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gcube.common.keycloak.model.util;
import java.io.IOException;

View File

@ -1,13 +1,32 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gcube.common.keycloak.model.util;
import java.util.Date;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class Time {
private static int offset;
/**
* Returns current time in seconds adjusted by adding {@link #offset) seconds.
* Returns current time in seconds adjusted by adding {@link #offset} seconds.
* @return see description
*/
public static int currentTime() {
@ -15,7 +34,7 @@ public class Time {
}
/**
* Returns current time in milliseconds adjusted by adding {@link #offset) seconds.
* Returns current time in milliseconds adjusted by adding {@link #offset} seconds.
* @return see description
*/
public static long currentTimeMillis() {

View File

@ -1,8 +1,13 @@
package org.gcube.common.keycloak;
import java.net.URL;
import static org.junit.Assert.assertTrue;
import java.net.URL;
import java.util.Collections;
import org.gcube.common.keycloak.model.JSONWebKeySet;
import org.gcube.common.keycloak.model.ModelUtils;
import org.gcube.common.keycloak.model.PublishedRealmRepresentation;
import org.gcube.common.keycloak.model.TokenIntrospectionResponse;
import org.gcube.common.keycloak.model.TokenResponse;
import org.junit.After;
@ -23,7 +28,11 @@ public class TestKeycloakClient {
protected static final String DEV_BASE_URL = "https://url.gcube.d4science.org/auth/realms/d4science";
protected static final String DEV_TOKEN_ENDPOINT = DEV_BASE_URL + "/protocol/openid-connect/token";
protected static final String DEV_INTROSPECTION_ENDPOINT = DEV_TOKEN_ENDPOINT + "/introspect";
protected static final String DEV_AVATAR_ENDPOINT = DEV_BASE_URL + "/account-avatar";
protected static final String ACCOUNTS_DEV_INTROSPECTION_ENDPOINT = "https://accounts.dev.d4science.org/auth/realms/d4science/protocol/openid-connect/token/introspect";
protected static final String TOKEN_RESTRICTION_VRE_CONTEXT = "%2Fgcube%2Fdevsec%2FCCP";
protected static final String CLIENT_ID = "keycloak-client-unit-test";
protected static final String CLIENT_SECRET = "ebf6f82e-9511-408e-8321-203081e472d8";
protected static final String TEST_AUDIENCE = "conductor-server";
@ -32,38 +41,41 @@ public class TestKeycloakClient {
protected static final String OLD_OIDC_ACCESS_TOKEN = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJSSklZNEpoNF9qdDdvNmREY0NlUDFfS1l0akcxVExXVW9oMkQ2Tzk1bFNBIn0.eyJleHAiOjE2NTI5Nzk4NDUsImlhdCI6MTY1Mjk3OTU0NSwianRpIjoiMzQ2MjgwMWItODg4NS00YTM4LWJkNDUtNWExM2U1MGE5MGU5IiwiaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5kZXYuZDRzY2llbmNlLm9yZy9hdXRoL3JlYWxtcy9kNHNjaWVuY2UiLCJhdWQiOlsiJTJGZ2N1YmUiLCIlMkZnY3ViZSUyRmRldnNlYyUyRmRldlZSRSIsImFjY291bnQiXSwic3ViIjoiYTQ3ZGZlMTYtYjRlZC00NGVkLWExZDktOTdlY2Q1MDQzNjBjIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoia2V5Y2xvYWstY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6ImQ4MDk3MDBmLWEyNDUtNDI3Zi1hYzhjLTQxYjFkZDNkYTQ3MCIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsIkluZnJhc3RydWN0dXJlLUNsaWVudCIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiJTJGZ2N1YmUiOnsicm9sZXMiOlsiTWVtYmVyIl19LCJrZXljbG9hay1jbGllbnQiOnsicm9sZXMiOlsidW1hX3Byb3RlY3Rpb24iXX0sIiUyRmdjdWJlJTJGZGV2c2VjJTJGZGV2VlJFIjp7InJvbGVzIjpbIk1lbWJlciJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiY2xpZW50SWQiOiJrZXljbG9hay1jbGllbnQiLCJjbGllbnRIb3N0IjoiOTMuNjYuMTg1Ljc1IiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJzZXJ2aWNlLWFjY291bnQta2V5Y2xvYWstY2xpZW50IiwiY2xpZW50QWRkcmVzcyI6IjkzLjY2LjE4NS43NSJ9.FQu4ox2HWeqeaY7nHYVGeJVpkJOcASfOb8tbOUeG-GB6sMjRB2S8PjLLaw63r_c42yxKszP04XdxGqIWqXTtoD9QCiUHTT5yJTkIpio4tMMGHth9Fbx-9dwk0yy_IFi1_OsCvZFmOQRdjMuUkj1lSqslCzAw-2E5q1Zt415-au5pEVJYNTFqIsG72ChJwh6eq1Dh1XBy8krb7YVPQyIwxO_awgAYO5hbsdvXYlRfCrnB38kk2V6-CQ-XYoL1m7xIB-gjhKCiFvDmmntQSRCZFgb0qi8eOmh9FdzPxZgx7yPJwAAj17dS4B_gz9FpZBVciNzpA6Lf4P2bqvoD9-R6ow";
protected static final String OLD_UMA_ACCESS_TOKEN = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJSSklZNEpoNF9qdDdvNmREY0NlUDFfS1l0akcxVExXVW9oMkQ2Tzk1bFNBIn0.eyJleHAiOjE2NTI5ODA0NzgsImlhdCI6MTY1Mjk4MDE3OCwianRpIjoiNjBkNzU3MGMtZmQxOC00NGQ1LTg1MzUtODhlMmFmOGQ1ZTgwIiwiaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5kZXYuZDRzY2llbmNlLm9yZy9hdXRoL3JlYWxtcy9kNHNjaWVuY2UiLCJhdWQiOiJjb25kdWN0b3Itc2VydmVyIiwic3ViIjoiYTQ3ZGZlMTYtYjRlZC00NGVkLWExZDktOTdlY2Q1MDQzNjBjIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoia2V5Y2xvYWstY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6IjI3NDUyN2M5LWNkZjMtNGM2Yi1iNTUxLTFmMTRkZGE5ZGVlZiIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiSW5mcmFzdHJ1Y3R1cmUtQ2xpZW50Il19LCJhdXRob3JpemF0aW9uIjp7InBlcm1pc3Npb25zIjpbeyJzY29wZXMiOlsiZ2V0Il0sInJzaWQiOiIyNDlmZDQ2OS03OWM1LTRiODUtYjE5NS1mMjliM2ViNjAzNDUiLCJyc25hbWUiOiJtZXRhZGF0YSJ9LHsic2NvcGVzIjpbImdldCIsInN0YXJ0IiwidGVybWluYXRlIl0sInJzaWQiOiJhNmYzZWFkZS03NDA0LTRlNWQtOTA3MC04MDBhZGI1YWFjNGUiLCJyc25hbWUiOiJ3b3JrZmxvdyJ9LHsicnNpZCI6IjFiNmMwMGI3LTkxMzktNGVhYS1hYWM3LTIwMjMxZmVlMDVhNSIsInJzbmFtZSI6IkRlZmF1bHQgUmVzb3VyY2UifV19LCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJjbGllbnRJZCI6ImtleWNsb2FrLWNsaWVudCIsImNsaWVudEhvc3QiOiI5My42Ni4xODUuNzUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC1rZXljbG9hay1jbGllbnQiLCJjbGllbnRBZGRyZXNzIjoiOTMuNjYuMTg1Ljc1In0.Hh62E56R-amHwoDPFQEylMvrvmNzWnC_4bDI7_iQYAPJ5YzCNH9d7zcdGaQ96kRmps_JRc2Giv_1W9kYorOhlXl-5QLDrSoqrqFxrNpEGG5r5jpNJbusbu4wNUKiCt_GMnM1UmztgXiQeuggNGkmeBIjotj0eubnmIbUV9ukHj3v7Z5PwNKKX3BCpsghd1u8lg6Nfqk_Oho4GXUfdaFY_AR3SNqzVI_9YLhND_a03MNNWlnfOvj8T4nDCKBZIs91tVyiu98d2TjnQt8PdlVwokMP3LA58m0Khy2cmUm1KF2k0zlzP8MxV9wTxNrpovMr-PnbtEPZ_IlVQIzHwjHfwQ";
protected static URL tokenURL;
protected static URL introspectionURL;
protected static TokenResponse oidcTR = null;
protected static TokenResponse umaTR = null;
@Before
public void setUp() throws Exception {
}
@After
public void tearDown() throws Exception {
// Assure to reset the factory's default base URL
KeycloakClientFactory.setCustomBaseURL(null);
}
@Test
public void test00TokenEndpointConstruction() throws Exception {
logger.info("*** [0.0] Start testing Keycloak token endpoint construction from base URL computed from context...");
logger.info(
"*** [0.0] Start testing Keycloak token endpoint construction from base URL computed from context...");
KeycloakClient client = KeycloakClientFactory.newInstance();
tokenURL = client.getTokenEndpointURL(client.getRealmBaseURL(DEV_ROOT_CONTEXT));
URL tokenURL = client.getTokenEndpointURL(client.getRealmBaseURL(DEV_ROOT_CONTEXT));
logger.info("*** [0.0] Constructed token URL is: {}", tokenURL);
URL devTokenURL = new URL(DEV_TOKEN_ENDPOINT);
logger.info("*** [0.0] DEV token URL is: {}", devTokenURL);
Assert.assertNotNull(tokenURL);
Assert.assertTrue(tokenURL.getProtocol().equals("https"));
Assert.assertEquals(new URL(DEV_TOKEN_ENDPOINT), tokenURL);
logger.info("Constructed URL is: {}", tokenURL);
Assert.assertEquals(tokenURL.getProtocol(), "https");
Assert.assertEquals(devTokenURL.toString(), tokenURL.toString());
}
@Test
public void test01IntrospectionEndpointConstruction() throws Exception {
logger.info("*** [0.0] Start testing Keycloak introspection endpoint construction from base URL...");
logger.info("*** [0.1] Start testing Keycloak introspection endpoint construction from base URL...");
KeycloakClient client = KeycloakClientFactory.newInstance();
introspectionURL = client.getIntrospectionEndpointURL(client.getRealmBaseURL(DEV_ROOT_CONTEXT));
URL introspectionURL = client.getIntrospectionEndpointURL(client.getRealmBaseURL(DEV_ROOT_CONTEXT));
logger.info("*** [0.1] Constructed introspection URL is: {}", introspectionURL);
URL devIntrospectionURL = new URL(DEV_INTROSPECTION_ENDPOINT);
logger.info("*** [0.1] DEV introspection URL is: {}", devIntrospectionURL);
Assert.assertNotNull(introspectionURL);
Assert.assertTrue(introspectionURL.getProtocol().equals("https"));
Assert.assertEquals(new URL(DEV_INTROSPECTION_ENDPOINT), introspectionURL);
Assert.assertEquals(introspectionURL.getProtocol(), "https");
Assert.assertEquals(devIntrospectionURL.toString(), introspectionURL.toString());
logger.info("Constructed URL is: {}", introspectionURL);
}
@ -71,158 +83,300 @@ public class TestKeycloakClient {
public void test02IntrospectEndpointCompute() throws Exception {
logger.info("*** [0.2] Start testing Keycloak userinfo endpoint computed from provided URL string...");
KeycloakClient client = KeycloakClientFactory.newInstance();
URL url = client
URL introspectionURL = client.getIntrospectionEndpointURL(client.getRealmBaseURL(DEV_ROOT_CONTEXT));
logger.info("*** [0.2] Constructed introspection URL is: {}", introspectionURL);
URL computedIntrospectionURL = client
.computeIntrospectionEndpointURL(client.getTokenEndpointURL(client.getRealmBaseURL(DEV_ROOT_CONTEXT)));
logger.info("*** [0.2] Computed introspection URL is: {}", computedIntrospectionURL);
URL devIntrospectionURL = new URL(DEV_INTROSPECTION_ENDPOINT);
logger.info("*** [0.2] DEV introspection URL is: {}", devIntrospectionURL);
Assert.assertNotNull(computedIntrospectionURL);
Assert.assertEquals(computedIntrospectionURL.getProtocol(), "https");
Assert.assertEquals(introspectionURL.toString(), computedIntrospectionURL.toString());
Assert.assertEquals(devIntrospectionURL.toString(), computedIntrospectionURL.toString());
}
Assert.assertNotNull(url);
Assert.assertTrue(url.getProtocol().equals("https"));
Assert.assertEquals(introspectionURL, url);
Assert.assertEquals(new URL(DEV_INTROSPECTION_ENDPOINT), url);
logger.info("Computed URL is: {}", url);
@Test
public void test03AvatarEndpointConstruction() throws Exception {
logger.info("*** [0.3] Start testing Keycloak avatar endpoint construction from base URL...");
KeycloakClient client = KeycloakClientFactory.newInstance();
URL avatarURL = client.getAvatarEndpointURL(client.getRealmBaseURL(DEV_ROOT_CONTEXT));
logger.info("*** [0.3] Constructed avatar URL is: {}", avatarURL);
URL devAvatarURL = new URL(DEV_AVATAR_ENDPOINT);
logger.info("*** [0.3] DEV avatar URL is: {}", devAvatarURL);
Assert.assertNotNull(avatarURL);
Assert.assertEquals(avatarURL.getProtocol(), "https");
Assert.assertEquals(devAvatarURL.toString(), avatarURL.toString());
}
@Test
public void test04CustomEndpointTest() throws Exception {
String customBase = "https://accounts.cloud.dev.d4science.org/auth/realms/";
logger.info("*** [0.4] Start testing Keycloak token endpoint construction from custom base URL {}...",
customBase);
KeycloakClientFactory.setCustomBaseURL(customBase);
KeycloakClient client = KeycloakClientFactory.newInstance();
URL customBaseURL = client.getRealmBaseURL(DEV_ROOT_CONTEXT);
logger.info("*** [0.4] Constructed token URL is: {}", customBaseURL);
URL devTokenURL = new URL(DEV_TOKEN_ENDPOINT);
logger.info("*** [0.4] DEV token URL is: {}", devTokenURL);
Assert.assertNotNull(customBaseURL);
Assert.assertEquals(customBaseURL.getProtocol(), "https");
Assert.assertEquals(customBase + KeycloakClient.DEFAULT_REALM + "/", customBaseURL.toString());
}
@Test
public void test10aQueryRealmInfo() throws Exception {
logger.info("*** [1.0a] Start testing query realm info...");
KeycloakClient client = KeycloakClientFactory.newInstance();
PublishedRealmRepresentation realmInfo = client.getRealmInfo(client.getRealmBaseURL(DEV_ROOT_CONTEXT));
logger.info("*** [1.0a] Realm info public key PEM: {}", realmInfo.getPublicKeyPem());
logger.info("*** [1.0a] Realm info public key: {}", realmInfo.getPublicKey());
}
@Test
public void test10bQueryRealmJWK() throws Exception {
logger.info("*** [1.0b] Start testing query realm JWK...");
KeycloakClient client = KeycloakClientFactory.newInstance();
JSONWebKeySet jsonWebKeySet = client
.getRealmJSONWebKeySet(client.getJWKEndpointURL(client.getRealmBaseURL(DEV_ROOT_CONTEXT)));
for (int i = 0; i < jsonWebKeySet.getKeys().length; i++) {
logger.info("*** [1.0b] Realm JWK public key {} algorithm: {}", i,
jsonWebKeySet.getKeys()[i].getAlgorithm());
}
}
@Test
public void test11TestAccessTokenJWTSignature() throws Exception {
logger.info("*** [1.0] Start testing access token JTW signature with model utils...");
KeycloakClient client = KeycloakClientFactory.newInstance();
PublishedRealmRepresentation realmInfo = client.getRealmInfo(client.getRealmBaseURL(DEV_ROOT_CONTEXT));
logger.info("*** [1.0] Realm info public key PEM: {}", realmInfo.getPublicKeyPem());
logger.info("*** [1.0] Realm info public key: {}", realmInfo.getPublicKey());
TokenResponse oidcTR = client.queryOIDCToken(DEV_ROOT_CONTEXT, CLIENT_ID, CLIENT_SECRET);
logger.info("*** [1.0] OIDC access token: {}", oidcTR.getAccessToken());
Assert.assertTrue("Access token is not valid",
ModelUtils.isValid(oidcTR.getAccessToken(), realmInfo.getPublicKey()));
}
@Test
public void test12QueryOIDCToken() throws Exception {
logger.info("*** [1.2] Start testing query OIDC token from Keycloak with context...");
oidcTR = KeycloakClientFactory.newInstance().queryOIDCToken(DEV_ROOT_CONTEXT, CLIENT_ID,
TokenResponse oidcTR = KeycloakClientFactory.newInstance().queryOIDCToken(DEV_ROOT_CONTEXT, CLIENT_ID,
CLIENT_SECRET);
logger.info("*** [1.2] OIDC access token: {}", oidcTR.getAccessToken());
logger.info("*** [1.2] OIDC refresh token: {}", oidcTR.getRefreshToken());
TestModels.checkTokenResponse(oidcTR);
TestModels.checkAccessToken(ModelUtils.getAccessTokenFrom(oidcTR), "service-account-" + CLIENT_ID, false);
TestModelUtils.checkTokenResponse(oidcTR);
TestModelUtils.checkAccessToken(ModelUtils.getAccessTokenFrom(oidcTR), "service-account-" + CLIENT_ID, false);
}
@Test
public void test12aQueryOIDCToken() throws Exception {
logger.info("*** [1.2a] Start testing query OIDC token from Keycloak with URL...");
oidcTR = KeycloakClientFactory.newInstance().queryOIDCToken(tokenURL, CLIENT_ID,
CLIENT_SECRET);
KeycloakClient client = KeycloakClientFactory.newInstance();
URL tokenURL = client.getTokenEndpointURL(client.getRealmBaseURL(DEV_ROOT_CONTEXT));
TokenResponse oidcTR = client.queryOIDCToken(tokenURL, CLIENT_ID, CLIENT_SECRET);
logger.info("*** [1.2a] OIDC access token: {}", oidcTR.getAccessToken());
logger.info("*** [1.2a] OIDC refresh token: {}", oidcTR.getRefreshToken());
TestModels.checkTokenResponse(oidcTR);
TestModels.checkAccessToken(ModelUtils.getAccessTokenFrom(oidcTR), "service-account-" + CLIENT_ID, false);
TestModelUtils.checkTokenResponse(oidcTR);
TestModelUtils.checkAccessToken(ModelUtils.getAccessTokenFrom(oidcTR), "service-account-" + CLIENT_ID, false);
}
@Test
public void test13QueryOIDCTokenOfUser() throws Exception {
logger.info("*** [1.3] Start testing query OIDC token from Keycloak with context for user...");
oidcTR = KeycloakClientFactory.newInstance().queryOIDCTokenOfUser(DEV_ROOT_CONTEXT, CLIENT_ID,
TokenResponse oidcTR = KeycloakClientFactory.newInstance().queryOIDCTokenOfUser(DEV_ROOT_CONTEXT, CLIENT_ID,
CLIENT_SECRET, TEST_USER_USERNAME, TEST_USER_PASSWORD);
logger.info("*** [1.3] OIDC access token: {}", oidcTR.getAccessToken());
logger.info("*** [1.3] OIDC refresh token: {}", oidcTR.getRefreshToken());
TestModels.checkTokenResponse(oidcTR);
TestModels.checkAccessToken(ModelUtils.getAccessTokenFrom(oidcTR), TEST_USER_USERNAME, false);
TestModelUtils.checkTokenResponse(oidcTR);
TestModelUtils.checkAccessToken(ModelUtils.getAccessTokenFrom(oidcTR), TEST_USER_USERNAME, false);
}
@Test
public void test13aQueryOIDCTokenOfUserWithContext() throws Exception {
logger.info("*** [1.3a] Start testing query OIDC token for audience from Keycloak with context for user...");
oidcTR = KeycloakClientFactory.newInstance().queryOIDCTokenOfUserWithContext(DEV_ROOT_CONTEXT, CLIENT_ID,
CLIENT_SECRET, TEST_USER_USERNAME, TEST_USER_PASSWORD, TEST_AUDIENCE);
TokenResponse oidcTR = KeycloakClientFactory.newInstance().queryOIDCTokenOfUser(DEV_ROOT_CONTEXT,
CLIENT_ID, CLIENT_SECRET, TEST_USER_USERNAME, TEST_USER_PASSWORD);
logger.info("*** [1.3a] OIDC access token: {}", oidcTR.getAccessToken());
logger.info("*** [1.3a] OIDC refresh token: {}", oidcTR.getRefreshToken());
TestModels.checkTokenResponse(oidcTR);
TestModels.checkAccessToken(ModelUtils.getAccessTokenFrom(oidcTR), TEST_USER_USERNAME, true);
TestModelUtils.checkTokenResponse(oidcTR);
TestModelUtils.checkAccessToken(ModelUtils.getAccessTokenFrom(oidcTR), TEST_USER_USERNAME, true);
TokenResponse oidcRestrictedTR = KeycloakClientFactory.newInstance().queryOIDCTokenOfUserWithContext(
DEV_ROOT_CONTEXT, CLIENT_ID, CLIENT_SECRET, TEST_USER_USERNAME, TEST_USER_PASSWORD,
TOKEN_RESTRICTION_VRE_CONTEXT);
logger.info("*** [1.3a] OIDC restricted access token: {}", oidcRestrictedTR.getAccessToken());
logger.info("*** [1.3a] OIDC restricted refresh token: {}", oidcRestrictedTR.getRefreshToken());
TestModelUtils.checkTokenResponse(oidcTR);
TestModelUtils.checkTokenResponse(oidcRestrictedTR);
TestModelUtils.checkAccessToken(ModelUtils.getAccessTokenFrom(oidcTR), TEST_USER_USERNAME, true);
TestModelUtils.checkAccessToken(ModelUtils.getAccessTokenFrom(oidcRestrictedTR), TEST_USER_USERNAME, true);
assertTrue(ModelUtils.getAccessTokenFrom(oidcTR).getAudience().length > 1);
assertTrue(ModelUtils.getAccessTokenFrom(oidcRestrictedTR).getAudience().length == 1);
assertTrue(
TOKEN_RESTRICTION_VRE_CONTEXT.equals(ModelUtils.getAccessTokenFrom(oidcRestrictedTR).getAudience()[0]));
}
@Test
public void test13bQueryOIDCTokenAndUMAOfUser() throws Exception {
logger.info("*** [1.3b] Start testing query OIDC and UMA tokens from Keycloak with context for user...");
oidcTR = KeycloakClientFactory.newInstance().queryOIDCTokenOfUser(DEV_ROOT_CONTEXT, CLIENT_ID,
CLIENT_SECRET, TEST_USER_USERNAME, TEST_USER_PASSWORD);
KeycloakClient client = KeycloakClientFactory.newInstance();
TokenResponse oidcTR = client.queryOIDCTokenOfUser(DEV_ROOT_CONTEXT, CLIENT_ID, CLIENT_SECRET,
TEST_USER_USERNAME, TEST_USER_PASSWORD);
logger.info("*** [1.3b] OIDC access token: {}", oidcTR.getAccessToken());
logger.info("*** [1.3b] OIDC refresh token: {}", oidcTR.getRefreshToken());
TestModels.checkTokenResponse(oidcTR);
TestModels.checkAccessToken(ModelUtils.getAccessTokenFrom(oidcTR), TEST_USER_USERNAME, false);
umaTR = KeycloakClientFactory.newInstance().queryUMAToken(DEV_ROOT_CONTEXT, oidcTR, TEST_AUDIENCE, null);
TestModelUtils.checkTokenResponse(oidcTR);
TestModelUtils.checkAccessToken(ModelUtils.getAccessTokenFrom(oidcTR), TEST_USER_USERNAME, false);
TokenResponse umaTR = client.queryUMAToken(DEV_ROOT_CONTEXT, oidcTR, TEST_AUDIENCE, null);
logger.info("*** [1.3b] UMA access token: {}", umaTR.getAccessToken());
logger.info("*** [1.3b] UMA refresh token: {}", umaTR.getRefreshToken());
TestModels.checkTokenResponse(umaTR);
TestModels.checkAccessToken(ModelUtils.getAccessTokenFrom(umaTR), TEST_USER_USERNAME, true);
TestModelUtils.checkTokenResponse(umaTR);
TestModelUtils.checkAccessToken(ModelUtils.getAccessTokenFrom(umaTR), TEST_USER_USERNAME, true);
}
@Test
public void test13aQueryOIDCTokenOfUserWithContextAndCustomHeader() throws Exception {
logger.info(
"*** [1.3c] Start testing query OIDC token for audience from Keycloak with context and custom headers for user...");
TokenResponse oidcTR = KeycloakClientFactory.newInstance().queryOIDCTokenOfUserWithContext(DEV_ROOT_CONTEXT,
CLIENT_ID, CLIENT_SECRET, TEST_USER_USERNAME, TEST_USER_PASSWORD, TOKEN_RESTRICTION_VRE_CONTEXT,
Collections.singletonMap("X_A_CUSTOM_HEADER", "HEADER_VALUE"));
logger.info("*** [1.3c] OIDC access token: {}", oidcTR.getAccessToken());
logger.info("*** [1.3c] OIDC refresh token: {}", oidcTR.getRefreshToken());
TestModelUtils.checkTokenResponse(oidcTR);
TestModelUtils.checkAccessToken(ModelUtils.getAccessTokenFrom(oidcTR), TEST_USER_USERNAME, true);
assertTrue(ModelUtils.getAccessTokenFrom(oidcTR).getAudience().length == 1);
// It is not possible to check programmatically if the header has been added to the call, See the logs if the Header is present.
}
@Test
public void test24QueryUMAToken() throws Exception {
logger.info("*** [2.4] Start testing query UMA token from Keycloak with context...");
umaTR = KeycloakClientFactory.newInstance().queryUMAToken(DEV_ROOT_CONTEXT, CLIENT_ID, CLIENT_SECRET,
TEST_AUDIENCE, null);
TokenResponse umaTR = KeycloakClientFactory.newInstance().queryUMAToken(DEV_ROOT_CONTEXT, CLIENT_ID,
CLIENT_SECRET, TEST_AUDIENCE, null);
logger.info("*** [2.4] UMA access token: {}", umaTR.getAccessToken());
logger.info("*** [2.4] UMA refresh token: {}", umaTR.getRefreshToken());
TestModels.checkTokenResponse(umaTR);
TestModels.checkAccessToken(ModelUtils.getAccessTokenFrom(umaTR), "service-account-" + CLIENT_ID, true);
TestModelUtils.checkTokenResponse(umaTR);
TestModelUtils.checkAccessToken(ModelUtils.getAccessTokenFrom(umaTR), "service-account-" + CLIENT_ID, true);
}
@Test
public void test24aQueryUMAToken() throws Exception {
logger.info("*** [2.4a] Start testing query UMA token from Keycloak with context...");
umaTR = KeycloakClientFactory.newInstance().queryUMAToken(tokenURL, CLIENT_ID, CLIENT_SECRET,
TEST_AUDIENCE, null);
logger.info("*** [2.4a] Start testing query UMA token from Keycloak with URL...");
KeycloakClient client = KeycloakClientFactory.newInstance();
URL tokenURL = client.getTokenEndpointURL(client.getRealmBaseURL(DEV_ROOT_CONTEXT));
TokenResponse umaTR = client.queryUMAToken(tokenURL, CLIENT_ID, CLIENT_SECRET, TEST_AUDIENCE, null);
logger.info("*** [2.4a] UMA access token: {}", umaTR.getAccessToken());
logger.info("*** [2.4a] UMA refresh token: {}", umaTR.getRefreshToken());
TestModels.checkTokenResponse(umaTR);
TestModels.checkAccessToken(ModelUtils.getAccessTokenFrom(umaTR), "service-account-" + CLIENT_ID, true);
TestModelUtils.checkTokenResponse(umaTR);
TestModelUtils.checkAccessToken(ModelUtils.getAccessTokenFrom(umaTR), "service-account-" + CLIENT_ID, true);
}
@Test
public void test302IntrospectOIDCAccessToken() throws Exception {
logger.info("*** [3.2] Start testing introspect OIDC access token...");
TokenIntrospectionResponse tir = KeycloakClientFactory.newInstance().introspectAccessToken(
DEV_ROOT_CONTEXT, CLIENT_ID, CLIENT_SECRET, oidcTR.getAccessToken());
KeycloakClient client = KeycloakClientFactory.newInstance();
URL tokenURL = client.getTokenEndpointURL(client.getRealmBaseURL(DEV_ROOT_CONTEXT));
TokenResponse oidcTR = client.queryOIDCToken(tokenURL, CLIENT_ID, CLIENT_SECRET);
TokenIntrospectionResponse tir = client.introspectAccessToken(DEV_ROOT_CONTEXT, CLIENT_ID, CLIENT_SECRET,
oidcTR.getAccessToken());
TestModels.checkTokenIntrospectionResponse(tir);
TestModelUtils.checkTokenIntrospectionResponse(tir);
}
@Test
public void test302aIntrospectOIDCAccessToken() throws Exception {
logger.info("*** [3.2a] Start testing introspect OIDC access token...");
TokenIntrospectionResponse tir = KeycloakClientFactory.newInstance().introspectAccessToken(
introspectionURL, CLIENT_ID, CLIENT_SECRET, oidcTR.getAccessToken());
KeycloakClient client = KeycloakClientFactory.newInstance();
URL tokenURL = client.getTokenEndpointURL(client.getRealmBaseURL(DEV_ROOT_CONTEXT));
TokenResponse oidcTR = client.queryOIDCToken(tokenURL, CLIENT_ID, CLIENT_SECRET);
URL introspectionURL = client.getIntrospectionEndpointURL(client.getRealmBaseURL(DEV_ROOT_CONTEXT));
TokenIntrospectionResponse tir = client.introspectAccessToken(introspectionURL, CLIENT_ID, CLIENT_SECRET,
oidcTR.getAccessToken());
TestModels.checkTokenIntrospectionResponse(tir);
TestModelUtils.checkTokenIntrospectionResponse(tir);
}
@Test
public void test302bIntrospectOIDCAccessTokenFromDifferentServer() throws Exception {
logger.info("*** [3.2b] Start testing introspect OIDC access token with different base URLs...");
KeycloakClient client = KeycloakClientFactory.newInstance();
URL tokenURL = client.getTokenEndpointURL(client.getRealmBaseURL(DEV_ROOT_CONTEXT));
TokenResponse oidcTR = client.queryOIDCToken(tokenURL, CLIENT_ID, CLIENT_SECRET);
logger.info("*** [3.2b] OIDC access token: {}", oidcTR.getAccessToken());
URL introspectionURL = new URL(ACCOUNTS_DEV_INTROSPECTION_ENDPOINT);
TokenIntrospectionResponse tir = client.introspectAccessToken(introspectionURL, CLIENT_ID, CLIENT_SECRET,
oidcTR.getAccessToken());
TestModelUtils.checkTokenIntrospectionResponse(tir, true);
}
@Test
public void test304IntrospectUMAAccessToken() throws Exception {
logger.info("*** [3.4] Start testing introspect UMA access token...");
TokenIntrospectionResponse tir = KeycloakClientFactory.newInstance().introspectAccessToken(
DEV_ROOT_CONTEXT, CLIENT_ID, CLIENT_SECRET, umaTR.getAccessToken());
KeycloakClient client = KeycloakClientFactory.newInstance();
TokenResponse umaTR = client.queryUMAToken(DEV_ROOT_CONTEXT, CLIENT_ID, CLIENT_SECRET, TEST_AUDIENCE, null);
TokenIntrospectionResponse tir = client.introspectAccessToken(DEV_ROOT_CONTEXT, CLIENT_ID, CLIENT_SECRET,
umaTR.getAccessToken());
TestModels.checkTokenIntrospectionResponse(tir);
TestModelUtils.checkTokenIntrospectionResponse(tir);
}
@Test
public void test304aIntrospectUMAAccessToken() throws Exception {
logger.info("*** [3.4a] Start testing introspect UMA access token...");
TokenIntrospectionResponse tir = KeycloakClientFactory.newInstance().introspectAccessToken(
introspectionURL, CLIENT_ID, CLIENT_SECRET, umaTR.getAccessToken());
KeycloakClient client = KeycloakClientFactory.newInstance();
TokenResponse umaTR = client.queryUMAToken(DEV_ROOT_CONTEXT, CLIENT_ID, CLIENT_SECRET, TEST_AUDIENCE, null);
URL introspectionURL = client.getIntrospectionEndpointURL(client.getRealmBaseURL(DEV_ROOT_CONTEXT));
TokenIntrospectionResponse tir = client.introspectAccessToken(introspectionURL, CLIENT_ID, CLIENT_SECRET,
umaTR.getAccessToken());
TestModels.checkTokenIntrospectionResponse(tir);
TestModelUtils.checkTokenIntrospectionResponse(tir);
}
@Test
public void test306OIDCAccessTokenVerification() throws Exception {
logger.info("*** [3.6] Start OIDC access token verification...");
Assert.assertTrue(KeycloakClientFactory.newInstance().isAccessTokenVerified(
DEV_ROOT_CONTEXT, CLIENT_ID, CLIENT_SECRET, oidcTR.getAccessToken()));
KeycloakClient client = KeycloakClientFactory.newInstance();
URL tokenURL = client.getTokenEndpointURL(client.getRealmBaseURL(DEV_ROOT_CONTEXT));
TokenResponse oidcTR = client.queryOIDCToken(tokenURL, CLIENT_ID, CLIENT_SECRET);
Assert.assertTrue(
client.isAccessTokenVerified(DEV_ROOT_CONTEXT, CLIENT_ID, CLIENT_SECRET, oidcTR.getAccessToken()));
}
@Test
public void test306aOIDCAccessTokenVerification() throws Exception {
logger.info("*** [3.6a] Start OIDC access token verification...");
Assert.assertTrue(KeycloakClientFactory.newInstance().isAccessTokenVerified(
introspectionURL, CLIENT_ID, CLIENT_SECRET, oidcTR.getAccessToken()));
KeycloakClient client = KeycloakClientFactory.newInstance();
URL tokenURL = client.getTokenEndpointURL(client.getRealmBaseURL(DEV_ROOT_CONTEXT));
TokenResponse oidcTR = client.queryOIDCToken(tokenURL, CLIENT_ID, CLIENT_SECRET);
URL introspectionURL = client.getIntrospectionEndpointURL(client.getRealmBaseURL(DEV_ROOT_CONTEXT));
Assert.assertTrue(
client.isAccessTokenVerified(introspectionURL, CLIENT_ID, CLIENT_SECRET, oidcTR.getAccessToken()));
}
@Test
@ -235,22 +389,29 @@ public class TestKeycloakClient {
@Test
public void test307aOIDCAccessTokenNonVerification() throws Exception {
logger.info("*** [3.7a] Start OIDC access token NON verification...");
Assert.assertFalse(KeycloakClientFactory.newInstance().isAccessTokenVerified(
introspectionURL, CLIENT_ID, CLIENT_SECRET, OLD_OIDC_ACCESS_TOKEN));
KeycloakClient client = KeycloakClientFactory.newInstance();
URL introspectionURL = client.getIntrospectionEndpointURL(client.getRealmBaseURL(DEV_ROOT_CONTEXT));
Assert.assertFalse(
client.isAccessTokenVerified(introspectionURL, CLIENT_ID, CLIENT_SECRET, OLD_OIDC_ACCESS_TOKEN));
}
@Test
public void test309UMAAccessTokenVerification() throws Exception {
logger.info("*** [3.9] Start UMA access token verification...");
Assert.assertTrue(KeycloakClientFactory.newInstance().isAccessTokenVerified(
DEV_ROOT_CONTEXT, CLIENT_ID, CLIENT_SECRET, umaTR.getAccessToken()));
KeycloakClient client = KeycloakClientFactory.newInstance();
TokenResponse umaTR = client.queryUMAToken(DEV_ROOT_CONTEXT, CLIENT_ID, CLIENT_SECRET, TEST_AUDIENCE, null);
Assert.assertTrue(
client.isAccessTokenVerified(DEV_ROOT_CONTEXT, CLIENT_ID, CLIENT_SECRET, umaTR.getAccessToken()));
}
@Test
public void test309aUMAAccessTokenVerification() throws Exception {
logger.info("*** [3.9a] Start UMA access token verification...");
Assert.assertTrue(KeycloakClientFactory.newInstance().isAccessTokenVerified(
introspectionURL, CLIENT_ID, CLIENT_SECRET, umaTR.getAccessToken()));
KeycloakClient client = KeycloakClientFactory.newInstance();
TokenResponse umaTR = client.queryUMAToken(DEV_ROOT_CONTEXT, CLIENT_ID, CLIENT_SECRET, TEST_AUDIENCE, null);
URL introspectionURL = client.getIntrospectionEndpointURL(client.getRealmBaseURL(DEV_ROOT_CONTEXT));
Assert.assertTrue(
client.isAccessTokenVerified(introspectionURL, CLIENT_ID, CLIENT_SECRET, umaTR.getAccessToken()));
}
@Test
@ -263,19 +424,122 @@ public class TestKeycloakClient {
@Test
public void test310aUMAAccessTokenNonVerification() throws Exception {
logger.info("*** [3.10a] Start UMA access token NON verification...");
Assert.assertFalse(KeycloakClientFactory.newInstance().isAccessTokenVerified(
introspectionURL, CLIENT_ID, CLIENT_SECRET, OLD_UMA_ACCESS_TOKEN));
KeycloakClient client = KeycloakClientFactory.newInstance();
URL introspectionURL = client.getIntrospectionEndpointURL(client.getRealmBaseURL(DEV_ROOT_CONTEXT));
Assert.assertFalse(
client.isAccessTokenVerified(introspectionURL, CLIENT_ID, CLIENT_SECRET, OLD_UMA_ACCESS_TOKEN));
}
@Test
public void test4GetTokenOfUser() throws Exception {
logger.info("*** [4] Start testing query token from Keycloak for user...");
TokenResponse tokenResponse = KeycloakClientHelper.getTokenForUser(DEV_ROOT_CONTEXT, TEST_USER_USERNAME, TEST_USER_PASSWORD);
TokenResponse tokenResponse = KeycloakClientHelper.getTokenForUser(DEV_ROOT_CONTEXT, TEST_USER_USERNAME,
TEST_USER_PASSWORD);
logger.info("*** [4] UMA access token: {}", tokenResponse.getAccessToken());
logger.info("*** [4] UMA refresh token: {}", tokenResponse.getRefreshToken());
TestModels.checkTokenResponse(tokenResponse);
TestModels.checkAccessToken(ModelUtils.getAccessTokenFrom(tokenResponse), TEST_USER_USERNAME, true);
TestModelUtils.checkTokenResponse(tokenResponse);
TestModelUtils.checkAccessToken(ModelUtils.getAccessTokenFrom(tokenResponse), TEST_USER_USERNAME, true);
}
@Test
public void test51ExchangeToken4Access() throws Exception {
logger.info("*** [5.1] Start testing token exchange for access token from Keycloak...");
KeycloakClient client = KeycloakClientFactory.newInstance();
TokenResponse oidcTR = client.queryOIDCTokenOfUser(DEV_ROOT_CONTEXT, CLIENT_ID, CLIENT_SECRET,
TEST_USER_USERNAME, TEST_USER_PASSWORD);
logger.info("*** [5.1] OIDC access token: {}", oidcTR.getAccessToken());
TokenResponse exchangedTR = client.exchangeTokenForAccessToken(DEV_ROOT_CONTEXT, oidcTR.getAccessToken(),
CLIENT_ID, CLIENT_SECRET, CLIENT_ID);
logger.info("*** [5.1] Exchanged access token: {}", exchangedTR.getAccessToken());
TestModelUtils.checkTokenResponse(exchangedTR, false);
Assert.assertNull(exchangedTR.getRefreshToken());
TestModelUtils.checkTokenIntrospectionResponse(client.introspectAccessToken(DEV_ROOT_CONTEXT, CLIENT_ID,
CLIENT_SECRET, exchangedTR.getAccessToken()));
}
@Test
public void test51aExchangeUMAToken4Access() throws Exception {
logger.info("*** [5.1a] Start testing UMA token exchange for access token from Keycloak...");
KeycloakClient client = KeycloakClientFactory.newInstance();
TokenResponse oidcTR = client.queryOIDCTokenOfUser(DEV_ROOT_CONTEXT, CLIENT_ID, CLIENT_SECRET,
TEST_USER_USERNAME, TEST_USER_PASSWORD);
logger.info("*** [5.1a] OIDC access token: {}", oidcTR.getAccessToken());
TokenResponse umaTR = client.queryUMAToken(DEV_ROOT_CONTEXT, oidcTR, TOKEN_RESTRICTION_VRE_CONTEXT, null);
logger.info("*** [5.1a] UMA access token: {}", umaTR.getAccessToken());
TokenResponse exchangedTR = client.exchangeTokenForAccessToken(DEV_ROOT_CONTEXT, umaTR.getAccessToken(),
CLIENT_ID, CLIENT_SECRET, CLIENT_ID);
logger.info("*** [5.1a] Exchanged access token: {}", exchangedTR.getAccessToken());
TestModelUtils.checkTokenResponse(exchangedTR, false);
Assert.assertNull(exchangedTR.getRefreshToken());
TestModelUtils.checkTokenIntrospectionResponse(client.introspectAccessToken(DEV_ROOT_CONTEXT, CLIENT_ID,
CLIENT_SECRET, exchangedTR.getAccessToken()));
}
@Test
public void test52ExchangeToken4refresh() throws Exception {
logger.info("*** [5.2] Start testing token exchange for refresh token from Keycloak...");
KeycloakClient client = KeycloakClientFactory.newInstance();
TokenResponse oidcTR = client.queryOIDCTokenOfUser(DEV_ROOT_CONTEXT, CLIENT_ID, CLIENT_SECRET,
TEST_USER_USERNAME, TEST_USER_PASSWORD);
logger.info("*** [5.2] OIDC access token: {}", oidcTR.getAccessToken());
TokenResponse exchangedTR = client.exchangeTokenForRefreshToken(DEV_ROOT_CONTEXT, oidcTR.getAccessToken(),
CLIENT_ID, CLIENT_SECRET, CLIENT_ID);
logger.info("*** [5.2] Exchanged access token: {}", exchangedTR.getAccessToken());
logger.info("*** [5.2] Exchanged refresh token: {}", exchangedTR.getRefreshToken());
TestModelUtils.checkTokenResponse(exchangedTR);
TestModelUtils.checkTokenIntrospectionResponse(
client.introspectAccessToken(DEV_ROOT_CONTEXT, CLIENT_ID, CLIENT_SECRET, exchangedTR.getAccessToken()));
}
@Test(expected = IllegalArgumentException.class)
public void test53ExchangeToken4Offline() throws Exception {
logger.info("*** [5.3] Start testing token exchange for offline token from Keycloak...");
KeycloakClient client = KeycloakClientFactory.newInstance();
TokenResponse oidcTR = client.queryOIDCTokenOfUser(DEV_ROOT_CONTEXT, CLIENT_ID, CLIENT_SECRET,
TEST_USER_USERNAME, TEST_USER_PASSWORD);
logger.info("*** [5.3] OIDC access token: {}", oidcTR.getAccessToken());
// TokenResponse exchangedTR =
client.exchangeTokenForOfflineToken(DEV_ROOT_CONTEXT, oidcTR.getAccessToken(),
CLIENT_ID, CLIENT_SECRET, CLIENT_ID);
// For the moment this part is not covered by tests
// logger.info("*** [5.3] Exchanged access token: {}", exchangedTR.getAccessToken());
// logger.info("*** [5.3] Exchanged refresh token: {}", exchangedTR.getRefreshToken());
// TestModelUtils.checkTokenResponse(exchangedTR, true);
// TestModelUtils.checkOfflineToken(exchangedTR);
//
// TestModelUtils.checkTokenIntrospectionResponse(client.introspectAccessToken(DEV_ROOT_CONTEXT, CLIENT_ID,
// CLIENT_SECRET, exchangedTR.getAccessToken()));
}
@Test
public void test6GetAvatar() throws Exception {
logger.info("*** [6] Start testing get user's avatar...");
KeycloakClient client = KeycloakClientFactory.newInstance();
TokenResponse oidcTR = client.queryOIDCTokenOfUser(DEV_ROOT_CONTEXT, CLIENT_ID, CLIENT_SECRET,
TEST_USER_USERNAME, TEST_USER_PASSWORD);
byte[] avatarData = client.getAvatarData(DEV_ROOT_CONTEXT, oidcTR);
Assert.assertNotNull("Avatar data is null", avatarData);
logger.info("*** [6] Avatar image of user is {} bytes", avatarData.length);
}
}

View File

@ -1,6 +1,9 @@
package org.gcube.common.keycloak;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.interfaces.RSAPublicKey;
import org.gcube.com.fasterxml.jackson.databind.ObjectMapper;
import org.gcube.common.keycloak.model.AccessToken;
@ -18,9 +21,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestModels {
public class TestModelUtils {
protected static final Logger logger = LoggerFactory.getLogger(TestModels.class);
protected static final Logger logger = LoggerFactory.getLogger(TestModelUtils.class);
@Before
public void setUp() throws Exception {
@ -30,6 +33,22 @@ public class TestModels {
public void tearDown() throws Exception {
}
@Test
public void testTokenValidity() throws Exception {
logger.info("Start testing access token valdity...");
TokenResponse tr = new ObjectMapper().readValue(new File("src/test/resources/oidc-token-response.json"),
TokenResponse.class);
RSAPublicKey publicKey = ModelUtils.createRSAPublicKey(
new String(Files.readAllBytes(Paths.get("src/test/resources/rsa-public-key.pem"))));
// Valid signature
Assert.assertFalse("Token is valid", ModelUtils.isValid(tr.getAccessToken(), publicKey));
Assert.assertFalse("Token is not expired", ModelUtils.isValid(tr.getAccessToken(), publicKey, true));
Assert.assertTrue("Token is valid", ModelUtils.isValid(tr.getAccessToken(), publicKey, false));
Assert.assertFalse("Token signature is valid", ModelUtils.isValid(tr.getAccessToken().replace("ZV9hY2Nlc3", "ZV9hY2Nlcc"), publicKey));
}
@Test
public void testTokenResponseForOIDC() throws Exception {
logger.info("Start testing OIDC token response object binding...");
@ -38,7 +57,7 @@ public class TestModels {
logger.debug("OIDC token response:\n{}", ModelUtils.toJSONString(tr, true));
checkTokenResponse(tr);
}
@Test
@ -86,10 +105,16 @@ public class TestModels {
}
public static void checkTokenResponse(TokenResponse tr) throws Exception {
checkTokenResponse(tr, true);
}
public static void checkTokenResponse(TokenResponse tr, boolean checkAlsoRefreshToken) throws Exception {
Assert.assertNotNull(tr);
Assert.assertEquals("bearer", tr.getTokenType().toLowerCase());
Assert.assertNotNull(tr.getAccessToken());
Assert.assertNotNull(tr.getRefreshToken());
Assert.assertNotNull("Access token is null", tr.getAccessToken());
if (checkAlsoRefreshToken) {
Assert.assertNotNull("Refresh token is null", tr.getRefreshToken());
}
}
public static void checkAccessToken(AccessToken at, String preferredUsername, boolean checkAudience) {
@ -99,19 +124,33 @@ public class TestModels {
Assert.assertEquals(preferredUsername, at.getPreferredUsername());
}
if (checkAudience) {
Assert.assertNotNull(at.getAudience());
Assert.assertNotNull("Audience is null", at.getAudience());
}
}
public static void checkRefreshToken(RefreshToken rt) {
logger.debug("Refresh token:\n{}", ModelUtils.toJSONString(rt, true));
Assert.assertNotNull(rt.getOtherClaims());
Assert.assertNotNull(rt.getAudience());
Assert.assertNotNull("Other claims are null", rt.getOtherClaims());
Assert.assertNotNull("Audience is null", rt.getAudience());
}
public static void checkOfflineToken(TokenResponse tr) throws Exception {
RefreshToken rt = ModelUtils.getRefreshTokenFrom(tr.getRefreshToken());
Assert.assertEquals("Offline", rt.getType());
Assert.assertNull("Expiration is not null", rt.getExp());
}
public static void checkTokenIntrospectionResponse(TokenIntrospectionResponse tir) {
checkTokenIntrospectionResponse(tir, true);
}
public static void checkTokenIntrospectionResponse(TokenIntrospectionResponse tir, boolean mustBeActive) {
logger.debug("Token introspection response :\n{}", ModelUtils.toJSONString(tir, true));
Assert.assertTrue(tir.isActive());
if (mustBeActive) {
Assert.assertTrue("Token is not active", tir.getActive());
} else {
Assert.assertFalse("Token is active", tir.getActive());
}
}
}

View File

@ -0,0 +1 @@
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjyJAmPx5K2eGYjvRmiBC8as3nF/jsYSUgBnlul9TNEdWSPuxTzntTb37xDGPmVRkNyOCJmRnBcI8GbrWz8SHJ643JTKp8yx4zDQCgLD72crb9ah/Tfu8KpDz3+FRuYLE4EvvRCGBnsFO2vSM02iTAp7nSToOCX4jCCrDMBUUJkIzuZIQUBTx8lvWl/M6LtQAqS7Gw3wsZSklRcvsR9qlCUxJW3cvhALt9wWrejSJ3LaR6TMaNa8k6Ojk6bJ3/5c6OifxYde0YjXIOaeMkkgnfoQvs4cyhvNxCXLx65+VKV4Dlts7cTgJvuodV0w/UhJGfUxgA9V6IQowmwHNBnYAMwIDAQAB