Added custom base URL set via factory (not automatically working cross environments) [#27234]
Better tests for exchange-token features
This commit is contained in:
parent
35c913db02
commit
726291ca55
|
@ -4,6 +4,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||||
|
|
||||||
## [v2.1.0-SNAPSHOT]
|
## [v2.1.0-SNAPSHOT]
|
||||||
- Added `token-exchange` support, also with `offline-token` scope, and methods to add extra headers during the OIDC token requests.
|
- Added `token-exchange` support, also with `offline-token` scope, and methods to add extra headers during the OIDC token requests.
|
||||||
|
- Added custom base URL set via factory (not automatically working cross environments) [#27234].
|
||||||
|
|
||||||
## [v2.0.0]
|
## [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).
|
- 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).
|
||||||
|
|
|
@ -52,7 +52,21 @@ public class DefaultKeycloakClient implements KeycloakClient {
|
||||||
|
|
||||||
protected final static String AUTHORIZATION_HEADER = "Authorization";
|
protected final static String AUTHORIZATION_HEADER = "Authorization";
|
||||||
|
|
||||||
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
|
@Override
|
||||||
public URL getRealmBaseURL(String context) throws KeycloakClientException {
|
public URL getRealmBaseURL(String context) throws KeycloakClientException {
|
||||||
|
@ -61,16 +75,20 @@ public class DefaultKeycloakClient implements KeycloakClient {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public URL getRealmBaseURL(String context, String realm) throws KeycloakClientException {
|
public URL getRealmBaseURL(String context, String realm) throws KeycloakClientException {
|
||||||
String urlString = BASE_URL + realm + "/";
|
String realmBaseURLString = null;
|
||||||
if (!context.startsWith(PROD_ROOT_SCOPE)) {
|
if (getCustomBaseURL() != null) {
|
||||||
String root = checkContext(context).split("/")[1];
|
realmBaseURLString = getCustomBaseURL() + realm + "/";
|
||||||
urlString = urlString.replace("url", "url." + root.replaceAll("\\.", "-"));
|
} 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 {
|
try {
|
||||||
return new URL(urlString);
|
return new URL(realmBaseURLString);
|
||||||
} catch (MalformedURLException e) {
|
} catch (MalformedURLException e) {
|
||||||
// That should be almost impossible
|
logger.error("Cannot create base URL from string: {}", realmBaseURLString, e);
|
||||||
logger.warn("Cannot create base URL from string: {}", urlString, e);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -192,7 +210,7 @@ public class DefaultKeycloakClient implements KeycloakClient {
|
||||||
String username, String password, String audience, Map<String, String> extraHeaders) throws KeycloakClientException {
|
String username, String password, String audience, Map<String, String> extraHeaders) throws KeycloakClientException {
|
||||||
|
|
||||||
return queryOIDCTokenOfUserWithContext(getTokenEndpointURL(getRealmBaseURL(context)), clientId, clientSecret,
|
return queryOIDCTokenOfUserWithContext(getTokenEndpointURL(getRealmBaseURL(context)), clientId, clientSecret,
|
||||||
username, password, null, extraHeaders);
|
username, password, audience, extraHeaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -7,9 +7,32 @@ public class KeycloakClientFactory {
|
||||||
|
|
||||||
protected static final Logger logger = LoggerFactory.getLogger(KeycloakClientFactory.class);
|
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() {
|
public static KeycloakClient newInstance() {
|
||||||
logger.debug("Instantiating a new keycloak client instance");
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -45,6 +45,8 @@ public class TestKeycloakClient {
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void tearDown() throws Exception {
|
public void tearDown() throws Exception {
|
||||||
|
// Assure to reset the factory's default base URL
|
||||||
|
KeycloakClientFactory.setCustomBaseURL(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -105,6 +107,23 @@ public class TestKeycloakClient {
|
||||||
Assert.assertEquals(devAvatarURL.toString(), avatarURL.toString());
|
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
|
@Test
|
||||||
public void test12QueryOIDCToken() throws Exception {
|
public void test12QueryOIDCToken() throws Exception {
|
||||||
logger.info("*** [1.2] Start testing query OIDC token from Keycloak with context...");
|
logger.info("*** [1.2] Start testing query OIDC token from Keycloak with context...");
|
||||||
|
@ -145,13 +164,28 @@ public class TestKeycloakClient {
|
||||||
@Test
|
@Test
|
||||||
public void test13aQueryOIDCTokenOfUserWithContext() throws Exception {
|
public void test13aQueryOIDCTokenOfUserWithContext() throws Exception {
|
||||||
logger.info("*** [1.3a] Start testing query OIDC token for audience from Keycloak with context for user...");
|
logger.info("*** [1.3a] Start testing query OIDC token for audience from Keycloak with context for user...");
|
||||||
TokenResponse oidcTR = KeycloakClientFactory.newInstance().queryOIDCTokenOfUserWithContext(DEV_ROOT_CONTEXT,
|
TokenResponse oidcTR = KeycloakClientFactory.newInstance().queryOIDCTokenOfUser(DEV_ROOT_CONTEXT,
|
||||||
CLIENT_ID, CLIENT_SECRET, TEST_USER_USERNAME, TEST_USER_PASSWORD, TEST_AUDIENCE);
|
CLIENT_ID, CLIENT_SECRET, TEST_USER_USERNAME, TEST_USER_PASSWORD);
|
||||||
|
|
||||||
logger.info("*** [1.3a] OIDC access token: {}", oidcTR.getAccessToken());
|
logger.info("*** [1.3a] OIDC access token: {}", oidcTR.getAccessToken());
|
||||||
logger.info("*** [1.3a] OIDC refresh token: {}", oidcTR.getRefreshToken());
|
logger.info("*** [1.3a] OIDC refresh token: {}", oidcTR.getRefreshToken());
|
||||||
TestModels.checkTokenResponse(oidcTR);
|
TestModels.checkTokenResponse(oidcTR);
|
||||||
TestModels.checkAccessToken(ModelUtils.getAccessTokenFrom(oidcTR), TEST_USER_USERNAME, true);
|
TestModels.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());
|
||||||
|
TestModels.checkTokenResponse(oidcTR);
|
||||||
|
TestModels.checkTokenResponse(oidcRestrictedTR);
|
||||||
|
TestModels.checkAccessToken(ModelUtils.getAccessTokenFrom(oidcTR), TEST_USER_USERNAME, true);
|
||||||
|
TestModels.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
|
@Test
|
||||||
|
@ -178,28 +212,18 @@ public class TestKeycloakClient {
|
||||||
@Test
|
@Test
|
||||||
public void test13aQueryOIDCTokenOfUserWithContextAndCustomHeader() throws Exception {
|
public void test13aQueryOIDCTokenOfUserWithContextAndCustomHeader() throws Exception {
|
||||||
logger.info(
|
logger.info(
|
||||||
"*** [1.3c] Start testing query OIDC token for audience from Keycloak with context and custom header for user...");
|
"*** [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,
|
TokenResponse oidcTR = KeycloakClientFactory.newInstance().queryOIDCTokenOfUserWithContext(DEV_ROOT_CONTEXT,
|
||||||
CLIENT_ID, CLIENT_SECRET, TEST_USER_USERNAME, TEST_USER_PASSWORD, TOKEN_RESTRICTION_VRE_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 access token: {}", oidcTR.getAccessToken());
|
||||||
logger.info("*** [1.3c] OIDC refresh token: {}", oidcTR.getRefreshToken());
|
logger.info("*** [1.3c] OIDC refresh token: {}", oidcTR.getRefreshToken());
|
||||||
|
|
||||||
TokenResponse oidcRestrictedTR = KeycloakClientFactory.newInstance().queryOIDCTokenOfUserWithContext(
|
|
||||||
DEV_ROOT_CONTEXT,
|
|
||||||
CLIENT_ID, CLIENT_SECRET, TEST_USER_USERNAME, TEST_USER_PASSWORD, TOKEN_RESTRICTION_VRE_CONTEXT,
|
|
||||||
Collections.singletonMap(KeycloakClient.D4S_CONTEXT_HEADER_NAME, TOKEN_RESTRICTION_VRE_CONTEXT));
|
|
||||||
|
|
||||||
logger.info("*** [1.3c] OIDC restricted access token: {}", oidcRestrictedTR.getAccessToken());
|
|
||||||
logger.info("*** [1.3c] OIDC restricted refresh token: {}", oidcRestrictedTR.getRefreshToken());
|
|
||||||
TestModels.checkTokenResponse(oidcTR);
|
TestModels.checkTokenResponse(oidcTR);
|
||||||
TestModels.checkTokenResponse(oidcRestrictedTR);
|
|
||||||
TestModels.checkAccessToken(ModelUtils.getAccessTokenFrom(oidcTR), TEST_USER_USERNAME, true);
|
TestModels.checkAccessToken(ModelUtils.getAccessTokenFrom(oidcTR), TEST_USER_USERNAME, true);
|
||||||
TestModels.checkAccessToken(ModelUtils.getAccessTokenFrom(oidcRestrictedTR), TEST_USER_USERNAME, true);
|
assertTrue(ModelUtils.getAccessTokenFrom(oidcTR).getAudience().length == 1);
|
||||||
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.
|
||||||
assertTrue(ModelUtils.getAccessTokenFrom(oidcRestrictedTR).getAudience().length == 1);
|
|
||||||
assertTrue(
|
|
||||||
TOKEN_RESTRICTION_VRE_CONTEXT.equals(ModelUtils.getAccessTokenFrom(oidcRestrictedTR).getAudience()[0]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -443,27 +467,28 @@ public class TestKeycloakClient {
|
||||||
client.introspectAccessToken(DEV_ROOT_CONTEXT, CLIENT_ID, CLIENT_SECRET, exchangedTR.getAccessToken()));
|
client.introspectAccessToken(DEV_ROOT_CONTEXT, CLIENT_ID, CLIENT_SECRET, exchangedTR.getAccessToken()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test53ExchangeToken4Offline() throws Exception {
|
public void test53ExchangeToken4Offline() throws Exception {
|
||||||
logger.info("*** [5.3] Start testing token exchange for offline token from Keycloak...");
|
logger.info("*** [5.3] Start testing token exchange for offline token from Keycloak...");
|
||||||
KeycloakClient client = KeycloakClientFactory.newInstance();
|
KeycloakClient client = KeycloakClientFactory.newInstance();
|
||||||
TokenResponse oidcTR = client.queryOIDCTokenOfUser(DEV_ROOT_CONTEXT, CLIENT_ID, CLIENT_SECRET, TEST_USER_USERNAME,
|
TokenResponse oidcTR = client.queryOIDCTokenOfUser(DEV_ROOT_CONTEXT, CLIENT_ID, CLIENT_SECRET,
|
||||||
TEST_USER_PASSWORD);
|
TEST_USER_USERNAME,
|
||||||
|
TEST_USER_PASSWORD);
|
||||||
logger.info("*** [5.3] OIDC access token: {}", oidcTR.getAccessToken());
|
|
||||||
|
logger.info("*** [5.3] OIDC access token: {}", oidcTR.getAccessToken());
|
||||||
TokenResponse exchangedTR = client.exchangeTokenForOfflineToken(DEV_ROOT_CONTEXT, oidcTR, CLIENT_ID,
|
|
||||||
CLIENT_SECRET, CLIENT_ID);
|
TokenResponse exchangedTR = client.exchangeTokenForOfflineToken(DEV_ROOT_CONTEXT, oidcTR, CLIENT_ID,
|
||||||
|
CLIENT_SECRET, CLIENT_ID);
|
||||||
logger.info("*** [5.3] Exchanged access token: {}", exchangedTR.getAccessToken());
|
|
||||||
logger.info("*** [5.3] Exchanged refresh token: {}", exchangedTR.getRefreshToken());
|
logger.info("*** [5.3] Exchanged access token: {}", exchangedTR.getAccessToken());
|
||||||
TestModels.checkTokenResponse(exchangedTR, true);
|
logger.info("*** [5.3] Exchanged refresh token: {}", exchangedTR.getRefreshToken());
|
||||||
TestModels.checkOfflineToken(exchangedTR);
|
TestModels.checkTokenResponse(exchangedTR, true);
|
||||||
|
TestModels.checkOfflineToken(exchangedTR);
|
||||||
TestModels.checkTokenIntrospectionResponse(client.introspectAccessToken(DEV_ROOT_CONTEXT, CLIENT_ID,
|
|
||||||
CLIENT_SECRET, exchangedTR.getAccessToken()));
|
TestModels.checkTokenIntrospectionResponse(client.introspectAccessToken(DEV_ROOT_CONTEXT, CLIENT_ID,
|
||||||
|
CLIENT_SECRET, exchangedTR.getAccessToken()));
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|
||||||
// @Test
|
// @Test
|
||||||
// public void test6GetAvatar() throws Exception {
|
// public void test6GetAvatar() throws Exception {
|
||||||
|
|
Loading…
Reference in New Issue