diff --git a/multiuser/keycloak/che-multiuser-keycloak-server/pom.xml b/multiuser/keycloak/che-multiuser-keycloak-server/pom.xml index 3ea5c7870aa..419930a301e 100644 --- a/multiuser/keycloak/che-multiuser-keycloak-server/pom.xml +++ b/multiuser/keycloak/che-multiuser-keycloak-server/pom.xml @@ -147,6 +147,11 @@ javax.servlet-api provided + + com.github.tomakehurst + wiremock-jre8-standalone + test + com.jayway.restassured rest-assured diff --git a/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/KeycloakJwkProvider.java b/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/KeycloakJwkProvider.java index 22f2135179c..688906d589f 100644 --- a/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/KeycloakJwkProvider.java +++ b/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/KeycloakJwkProvider.java @@ -19,7 +19,6 @@ import javax.inject.Inject; import javax.inject.Provider; import org.eclipse.che.inject.ConfigurationException; -import org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants; /** Constructs {@link UrlJwkProvider} based on Jwk endpoint from keycloak settings */ public class KeycloakJwkProvider implements Provider { @@ -27,10 +26,8 @@ public class KeycloakJwkProvider implements Provider { private final JwkProvider jwkProvider; @Inject - public KeycloakJwkProvider(KeycloakSettings keycloakSettings) throws MalformedURLException { - - final String jwksUrl = - keycloakSettings.getInternalSettings().get(KeycloakConstants.JWKS_ENDPOINT_SETTING); + public KeycloakJwkProvider(OIDCInfo oidcInfo) throws MalformedURLException { + final String jwksUrl = oidcInfo.getJwksUri(); if (jwksUrl == null) { throw new ConfigurationException("Jwks endpoint url not found in keycloak settings"); diff --git a/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/KeycloakProfileRetriever.java b/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/KeycloakProfileRetriever.java index f09e16a9fad..61ff00ffa6f 100644 --- a/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/KeycloakProfileRetriever.java +++ b/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/KeycloakProfileRetriever.java @@ -19,7 +19,6 @@ import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.rest.HttpJsonRequestFactory; -import org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,11 +35,9 @@ public class KeycloakProfileRetriever { private final HttpJsonRequestFactory requestFactory; @Inject - public KeycloakProfileRetriever( - KeycloakSettings keycloakSettings, HttpJsonRequestFactory requestFactory) { + public KeycloakProfileRetriever(OIDCInfo oidcInfo, HttpJsonRequestFactory requestFactory) { this.requestFactory = requestFactory; - this.keyclockCurrentUserInfoUrl = - keycloakSettings.getInternalSettings().get(KeycloakConstants.USERINFO_ENDPOINT_SETTING); + this.keyclockCurrentUserInfoUrl = oidcInfo.getUserInfoEndpoint(); } /** diff --git a/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/KeycloakServiceClient.java b/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/KeycloakServiceClient.java index 34bc4626968..ffc5d59cbaf 100644 --- a/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/KeycloakServiceClient.java +++ b/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/KeycloakServiceClient.java @@ -11,7 +11,6 @@ */ package org.eclipse.che.multiuser.keycloak.server; -import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.AUTH_SERVER_URL_INTERNAL_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.REALM_SETTING; import com.google.common.io.CharStreams; @@ -62,6 +61,7 @@ public class KeycloakServiceClient { private KeycloakSettings keycloakSettings; + private final OIDCInfo oidcInfo; private static final Pattern assotiateUserPattern = Pattern.compile("User (.+) is not associated with identity provider (.+)"); @@ -70,8 +70,10 @@ public class KeycloakServiceClient { private JwtParser jwtParser; @Inject - public KeycloakServiceClient(KeycloakSettings keycloakSettings, JwtParser jwtParser) { + public KeycloakServiceClient( + KeycloakSettings keycloakSettings, OIDCInfo oidcInfo, JwtParser jwtParser) { this.keycloakSettings = keycloakSettings; + this.oidcInfo = oidcInfo; this.jwtParser = jwtParser; } @@ -101,8 +103,7 @@ public String getAccountLinkingURL( byte[] check = md.digest(input.getBytes(StandardCharsets.UTF_8)); final String hash = Base64.getUrlEncoder().encodeToString(check); - return UriBuilder.fromUri( - keycloakSettings.getInternalSettings().get(AUTH_SERVER_URL_INTERNAL_SETTING)) + return UriBuilder.fromUri(oidcInfo.getAuthServerURL()) .path("/realms/{realm}/broker/{provider}/link") .queryParam("nonce", nonce) .queryParam("hash", hash) @@ -128,8 +129,7 @@ public KeycloakTokenResponse getIdentityProviderToken(String oauthProvider) throws ForbiddenException, BadRequestException, IOException, NotFoundException, ServerException, UnauthorizedException { String url = - UriBuilder.fromUri( - keycloakSettings.getInternalSettings().get(AUTH_SERVER_URL_INTERNAL_SETTING)) + UriBuilder.fromUri(oidcInfo.getAuthServerURL()) .path("/realms/{realm}/broker/{provider}/token") .build(keycloakSettings.get().get(REALM_SETTING), oauthProvider) .toString(); diff --git a/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/KeycloakSettings.java b/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/KeycloakSettings.java index 1b3a7b6c8d6..f7a64d4fa6b 100644 --- a/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/KeycloakSettings.java +++ b/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/KeycloakSettings.java @@ -11,8 +11,6 @@ */ package org.eclipse.che.multiuser.keycloak.server; -import static com.google.common.base.MoreObjects.firstNonNull; -import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.AUTH_SERVER_URL_INTERNAL_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.AUTH_SERVER_URL_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.CLIENT_ID_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.FIXED_REDIRECT_URL_FOR_DASHBOARD; @@ -32,112 +30,44 @@ import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.USE_FIXED_REDIRECT_URLS_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.USE_NONCE_SETTING; -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Maps; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; import java.util.Collections; import java.util.Map; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.commons.annotation.Nullable; -import org.eclipse.che.commons.proxy.ProxyAuthenticator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** @author Max Shaposhnik (mshaposh@redhat.com) */ @Singleton public class KeycloakSettings { - private static final Logger LOG = LoggerFactory.getLogger(KeycloakSettings.class); - private static final String DEFAULT_USERNAME_CLAIM = "preferred_username"; + protected static final String DEFAULT_USERNAME_CLAIM = "preferred_username"; - /** - * Public Keycloak connection settings. It contains information about keycloak api urls and - * information required to make Keycloak connection using public domain hostname. This info will - * be shared with frontend. - */ private final Map settings; - /** - * Internal network Keycloak connection settings. It contains information about keycloak api urls - * and information required to make connection using k8s/openshift internal services hostname. - * This info will be used only on the Che server side. If using internal network is disabled, then - * will be included settings with public domain hostname. - */ - private final Map internalSettings; + private final String oidcProviderUrl; @Inject public KeycloakSettings( @Named("che.api") String cheServerEndpoint, @Nullable @Named(JS_ADAPTER_URL_SETTING) String jsAdapterUrl, @Nullable @Named(AUTH_SERVER_URL_SETTING) String serverURL, - @Nullable @Named(AUTH_SERVER_URL_INTERNAL_SETTING) String serverInternalURL, @Nullable @Named(REALM_SETTING) String realm, @Named(CLIENT_ID_SETTING) String clientId, - @Nullable @Named(OIDC_PROVIDER_SETTING) String oidcProvider, + @Nullable @Named(OIDC_PROVIDER_SETTING) String oidcProviderUrl, @Nullable @Named(USERNAME_CLAIM_SETTING) String usernameClaim, @Named(USE_NONCE_SETTING) boolean useNonce, @Nullable @Named(OSO_ENDPOINT_SETTING) String osoEndpoint, @Nullable @Named(GITHUB_ENDPOINT_SETTING) String gitHubEndpoint, - @Named(USE_FIXED_REDIRECT_URLS_SETTING) boolean useFixedRedirectUrls) { - - serverInternalURL = (serverInternalURL != null) ? serverInternalURL : serverURL; - - if (serverURL == null && serverInternalURL == null && oidcProvider == null) { - throw new RuntimeException( - "Either the '" - + AUTH_SERVER_URL_SETTING - + "'or'" - + AUTH_SERVER_URL_INTERNAL_SETTING - + "' or '" - + OIDC_PROVIDER_SETTING - + "' property should be set"); - } - - if (oidcProvider == null && realm == null) { - throw new RuntimeException("The '" + REALM_SETTING + "' property should be set"); - } - - String wellKnownEndpoint = firstNonNull(oidcProvider, serverInternalURL + "/realms/" + realm); - if (!wellKnownEndpoint.endsWith("/")) { - wellKnownEndpoint = wellKnownEndpoint + "/"; - } - wellKnownEndpoint += ".well-known/openid-configuration"; - - LOG.info("Retrieving OpenId configuration from endpoint: {}", wellKnownEndpoint); - - Map openIdConfiguration; - ProxyAuthenticator.initAuthenticator(wellKnownEndpoint); - try (InputStream inputStream = new URL(wellKnownEndpoint).openStream()) { - final JsonFactory factory = new JsonFactory(); - final JsonParser parser = factory.createParser(inputStream); - final TypeReference> typeReference = - new TypeReference>() {}; - openIdConfiguration = new ObjectMapper().reader().readValue(parser, typeReference); - } catch (IOException e) { - throw new RuntimeException( - "Exception while retrieving OpenId configuration from endpoint: " + wellKnownEndpoint, e); - } finally { - ProxyAuthenticator.resetAuthenticator(); - } - - LOG.info("openid configuration = {}", openIdConfiguration); + @Named(USE_FIXED_REDIRECT_URLS_SETTING) boolean useFixedRedirectUrls, + OIDCInfo oidcInfo) { + this.oidcProviderUrl = oidcProviderUrl; Map settings = Maps.newHashMap(); - Map internalSettings = Maps.newHashMap(); settings.put( USERNAME_CLAIM_SETTING, usernameClaim == null ? DEFAULT_USERNAME_CLAIM : usernameClaim); settings.put(CLIENT_ID_SETTING, clientId); settings.put(REALM_SETTING, realm); - if (serverInternalURL != null) { - internalSettings.put(AUTH_SERVER_URL_INTERNAL_SETTING, serverInternalURL); - } - if (serverURL != null) { settings.put(AUTH_SERVER_URL_SETTING, serverURL); settings.put(PROFILE_ENDPOINT_SETTING, serverURL + "/realms/" + realm + "/account"); @@ -149,37 +79,38 @@ public KeycloakSettings( TOKEN_ENDPOINT_SETTING, serverURL + "/realms/" + realm + "/protocol/openid-connect/token"); } - String endSessionEndpoint = (String) openIdConfiguration.get("end_session_endpoint"); - if (endSessionEndpoint != null) { - settings.put(LOGOUT_ENDPOINT_SETTING, endSessionEndpoint); + + if (oidcInfo.getEndSessionPublicEndpoint() != null) { + settings.put(LOGOUT_ENDPOINT_SETTING, oidcInfo.getEndSessionPublicEndpoint()); } - String tokenEndpoint = (String) openIdConfiguration.get("token_endpoint"); - if (tokenEndpoint != null) { - settings.put(TOKEN_ENDPOINT_SETTING, tokenEndpoint); + if (oidcInfo.getTokenPublicEndpoint() != null) { + settings.put(TOKEN_ENDPOINT_SETTING, oidcInfo.getTokenPublicEndpoint()); } - - String userInfoEndpoint = (String) openIdConfiguration.get("userinfo_endpoint"); - if (userInfoEndpoint != null) { - settings.put(USERINFO_ENDPOINT_SETTING, userInfoEndpoint); - if (serverURL != null) { - String internalInfoEndpoint = userInfoEndpoint.replace(serverURL, serverInternalURL); - internalSettings.put(USERINFO_ENDPOINT_SETTING, internalInfoEndpoint); - } + if (oidcInfo.getUserInfoPublicEndpoint() != null) { + settings.put(USERINFO_ENDPOINT_SETTING, oidcInfo.getUserInfoPublicEndpoint()); } - String jwksUriEndpoint = (String) openIdConfiguration.get("jwks_uri"); - if (jwksUriEndpoint != null) { - settings.put(JWKS_ENDPOINT_SETTING, jwksUriEndpoint); - if (serverURL != null) { - String internalJwksUriEndpoint = jwksUriEndpoint.replace(serverURL, serverInternalURL); - internalSettings.put(JWKS_ENDPOINT_SETTING, internalJwksUriEndpoint); - } + if (oidcInfo.getJwksPublicUri() != null) { + settings.put(JWKS_ENDPOINT_SETTING, oidcInfo.getJwksPublicUri()); } settings.put(OSO_ENDPOINT_SETTING, osoEndpoint); settings.put(GITHUB_ENDPOINT_SETTING, gitHubEndpoint); - if (oidcProvider != null) { - settings.put(OIDC_PROVIDER_SETTING, oidcProvider); + this.setUpKeycloakJSAdaptersURLS( + settings, useNonce, useFixedRedirectUrls, jsAdapterUrl, cheServerEndpoint, serverURL); + + this.settings = Collections.unmodifiableMap(settings); + } + + private void setUpKeycloakJSAdaptersURLS( + Map settings, + boolean useNonce, + boolean useFixedRedirectUrls, + String jsAdapterUrl, + String cheServerEndpoint, + String serverURL) { + if (oidcProviderUrl != null) { + settings.put(OIDC_PROVIDER_SETTING, oidcProviderUrl); if (useFixedRedirectUrls) { String rootUrl = cheServerEndpoint.endsWith("/") ? cheServerEndpoint : cheServerEndpoint + "/"; @@ -188,22 +119,24 @@ public KeycloakSettings( settings.put(FIXED_REDIRECT_URL_FOR_IDE, rootUrl + "keycloak/oidcCallbackIde.html"); } } + settings.put(USE_NONCE_SETTING, Boolean.toString(useNonce)); + if (jsAdapterUrl == null) { jsAdapterUrl = - (oidcProvider != null) ? "/api/keycloak/OIDCKeycloak.js" : serverURL + "/js/keycloak.js"; + (oidcProviderUrl != null) + ? "/api/keycloak/OIDCKeycloak.js" + : serverURL + "/js/keycloak.js"; } settings.put(JS_ADAPTER_URL_SETTING, jsAdapterUrl); - - this.settings = Collections.unmodifiableMap(settings); - this.internalSettings = Collections.unmodifiableMap(internalSettings); } + /** + * Public Keycloak connection settings. It contains information about keycloak api urls and + * information required to make Keycloak connection using public domain hostname. This info will + * be shared with frontend. + */ public Map get() { return settings; } - - public Map getInternalSettings() { - return internalSettings; - } } diff --git a/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/OIDCInfo.java b/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/OIDCInfo.java new file mode 100644 index 00000000000..ce2c6adc89b --- /dev/null +++ b/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/OIDCInfo.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +package org.eclipse.che.multiuser.keycloak.server; + +/** OIDCInfo - POJO object to store information about Keycloak api. */ +public class OIDCInfo { + + private final String tokenPublicEndpoint; + private final String endSessionPublicEndpoint; + private final String userInfoPublicEndpoint; + private final String userInfoEndpoint; + private final String jwksPublicUri; + private final String jwksUri; + private final String authServerURL; + + public OIDCInfo( + String tokenPublicEndpoint, + String endSessionPublicEndpoint, + String userInfoPublicEndpoint, + String userInfoEndpoint, + String jwksPublicUri, + String jwksUri, + String authServerURL) { + this.tokenPublicEndpoint = tokenPublicEndpoint; + this.endSessionPublicEndpoint = endSessionPublicEndpoint; + this.userInfoPublicEndpoint = userInfoPublicEndpoint; + this.userInfoEndpoint = userInfoEndpoint; + this.jwksPublicUri = jwksPublicUri; + this.jwksUri = jwksUri; + + this.authServerURL = authServerURL; + } + + /** @return public url to retrieve token */ + public String getTokenPublicEndpoint() { + return tokenPublicEndpoint; + } + + /** @return public log out url. */ + public String getEndSessionPublicEndpoint() { + return endSessionPublicEndpoint; + } + + /** @return public url to get user profile information. */ + public String getUserInfoPublicEndpoint() { + return userInfoPublicEndpoint; + } + + /** + * @return url to get user profile information. Url will be internal if internal network enabled, + * otherwise url will be public. + */ + public String getUserInfoEndpoint() { + return userInfoEndpoint; + } + + /** @return public url to retrieve JWK public key for token validation. */ + public String getJwksPublicUri() { + return jwksPublicUri; + } + + /** + * @return url to retrieve JWK public key for token validation. Url will be internal if internal + * network enabled, otherwise url will be public. + */ + public String getJwksUri() { + return jwksUri; + } + + /** + * @return OIDC auth endpoint url. Url will be internal if internal network enabled, otherwise url + * will be public. + */ + public String getAuthServerURL() { + return authServerURL; + } +} diff --git a/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/OIDCInfoProvider.java b/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/OIDCInfoProvider.java new file mode 100644 index 00000000000..f0ccf5a8259 --- /dev/null +++ b/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/OIDCInfoProvider.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.keycloak.server; + +import static com.google.common.base.MoreObjects.firstNonNull; +import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.*; +import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.REALM_SETTING; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Map; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; +import org.eclipse.che.commons.annotation.Nullable; +import org.eclipse.che.commons.proxy.ProxyAuthenticator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * OIDCInfoProvider retrieves OpenID Connect (OIDC) configuration for well-known endpoint. These + * information is useful to provide access to the Keycloak api. + */ +public class OIDCInfoProvider implements Provider { + + private static final Logger LOG = LoggerFactory.getLogger(OIDCInfoProvider.class); + + @Inject + @Nullable + @Named(AUTH_SERVER_URL_SETTING) + protected String serverURL; + + @Inject + @Nullable + @Named(AUTH_SERVER_URL_INTERNAL_SETTING) + protected String serverInternalURL; + + @Inject + @Nullable + @Named(OIDC_PROVIDER_SETTING) + protected String oidcProviderUrl; + + @Inject + @Nullable + @Named(REALM_SETTING) + protected String realm; + + /** @return OIDCInfo with OIDC settings information. */ + @Override + public OIDCInfo get() { + this.validate(); + + String serverAuthUrl = (serverInternalURL != null) ? serverInternalURL : serverURL; + String wellKnownEndpoint = this.getWellKnownEndpoint(serverAuthUrl); + + LOG.info("Retrieving OpenId configuration from endpoint: {}", wellKnownEndpoint); + ProxyAuthenticator.initAuthenticator(wellKnownEndpoint); + try (InputStream inputStream = new URL(wellKnownEndpoint).openStream()) { + final JsonParser parser = new JsonFactory().createParser(inputStream); + final TypeReference> typeReference = new TypeReference<>() {}; + + Map openIdConfiguration = + new ObjectMapper().reader().readValue(parser, typeReference); + + LOG.info("openid configuration = {}", openIdConfiguration); + + String tokenPublicEndPoint = (String) openIdConfiguration.get("token_endpoint"); + String userInfoPublicEndpoint = (String) openIdConfiguration.get("userinfo_endpoint"); + String endSessionPublicEndpoint = (String) openIdConfiguration.get("end_session_endpoint"); + String jwksPublicUri = (String) openIdConfiguration.get("jwks_uri"); + String jwksUri = setInternalUrl(jwksPublicUri); + String userInfoEndpoint = setInternalUrl(userInfoPublicEndpoint); + + return new OIDCInfo( + tokenPublicEndPoint, + endSessionPublicEndpoint, + userInfoPublicEndpoint, + userInfoEndpoint, + jwksPublicUri, + jwksUri, + serverAuthUrl); + } catch (IOException e) { + throw new RuntimeException( + "Exception while retrieving OpenId configuration from endpoint: " + wellKnownEndpoint, e); + } finally { + ProxyAuthenticator.resetAuthenticator(); + } + } + + private String getWellKnownEndpoint(String serverAuthUrl) { + String wellKnownEndpoint = firstNonNull(oidcProviderUrl, serverAuthUrl + "/realms/" + realm); + if (!wellKnownEndpoint.endsWith("/")) { + wellKnownEndpoint = wellKnownEndpoint + "/"; + } + wellKnownEndpoint += ".well-known/openid-configuration"; + return wellKnownEndpoint; + } + + private void validate() { + if (serverURL == null && serverInternalURL == null && oidcProviderUrl == null) { + throw new RuntimeException( + "Either the '" + + AUTH_SERVER_URL_SETTING + + "' or '" + + AUTH_SERVER_URL_INTERNAL_SETTING + + "' or '" + + OIDC_PROVIDER_SETTING + + "' property should be set"); + } + + if (oidcProviderUrl == null && realm == null) { + throw new RuntimeException("The '" + REALM_SETTING + "' property should be set"); + } + } + + private String setInternalUrl(String endpointUrl) { + if (serverURL != null && serverInternalURL != null) { + return endpointUrl.replace(serverURL, serverInternalURL); + } + return endpointUrl; + } +} diff --git a/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/deploy/KeycloakModule.java b/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/deploy/KeycloakModule.java index 642d0765238..a039a69e6e6 100644 --- a/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/deploy/KeycloakModule.java +++ b/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/deploy/KeycloakModule.java @@ -23,6 +23,8 @@ import org.eclipse.che.multiuser.keycloak.server.KeycloakJwtParserProvider; import org.eclipse.che.multiuser.keycloak.server.KeycloakTokenValidator; import org.eclipse.che.multiuser.keycloak.server.KeycloakUserManager; +import org.eclipse.che.multiuser.keycloak.server.OIDCInfo; +import org.eclipse.che.multiuser.keycloak.server.OIDCInfoProvider; import org.eclipse.che.multiuser.keycloak.server.dao.KeycloakProfileDao; import org.eclipse.che.security.oauth.OAuthAPI; @@ -38,6 +40,7 @@ protected void configure() { bind(ProfileDao.class).to(KeycloakProfileDao.class); bind(JwkProvider.class).toProvider(KeycloakJwkProvider.class); bind(JwtParser.class).toProvider(KeycloakJwtParserProvider.class); + bind(OIDCInfo.class).toProvider(OIDCInfoProvider.class).asEagerSingleton(); bind(PersonalAccountUserManager.class).to(KeycloakUserManager.class); bind(OAuthAPI.class).toProvider(OAuthAPIProvider.class); diff --git a/multiuser/keycloak/che-multiuser-keycloak-server/src/test/java/org/eclipse/che/multiuser/keycloak/server/KeycloakServiceClientTest.java b/multiuser/keycloak/che-multiuser-keycloak-server/src/test/java/org/eclipse/che/multiuser/keycloak/server/KeycloakServiceClientTest.java index e0b626591f3..28fd9f58d9b 100644 --- a/multiuser/keycloak/che-multiuser-keycloak-server/src/test/java/org/eclipse/che/multiuser/keycloak/server/KeycloakServiceClientTest.java +++ b/multiuser/keycloak/che-multiuser-keycloak-server/src/test/java/org/eclipse/che/multiuser/keycloak/server/KeycloakServiceClientTest.java @@ -54,6 +54,7 @@ public class KeycloakServiceClientTest { @Mock private KeycloakSettings keycloakSettings; @Mock private JwtParser jwtParser; + @Mock private OIDCInfo oidcInfo; private KeycloakServiceClient keycloakServiceClient; @@ -65,14 +66,17 @@ public class KeycloakServiceClientTest { @BeforeMethod public void setUp() throws Exception { - keycloakServiceClient = new KeycloakServiceClient(keycloakSettings, jwtParser); + when(oidcInfo.getAuthServerURL()) + .thenReturn(RestAssured.baseURI + ":" + RestAssured.port + RestAssured.basePath); + + keycloakServiceClient = new KeycloakServiceClient(keycloakSettings, oidcInfo, jwtParser); Map conf = new HashMap<>(); Map confInternal = new HashMap<>(); confInternal.put( AUTH_SERVER_URL_INTERNAL_SETTING, RestAssured.baseURI + ":" + RestAssured.port + RestAssured.basePath); conf.put(REALM_SETTING, "che"); - when(keycloakSettings.getInternalSettings()).thenReturn(confInternal); + when(keycloakSettings.get()).thenReturn(confInternal); when(keycloakSettings.get()).thenReturn(conf); } diff --git a/multiuser/keycloak/che-multiuser-keycloak-server/src/test/java/org/eclipse/che/multiuser/keycloak/server/KeycloakSettingsTest.java b/multiuser/keycloak/che-multiuser-keycloak-server/src/test/java/org/eclipse/che/multiuser/keycloak/server/KeycloakSettingsTest.java index 91f4de7b630..12d71304fd3 100644 --- a/multiuser/keycloak/che-multiuser-keycloak-server/src/test/java/org/eclipse/che/multiuser/keycloak/server/KeycloakSettingsTest.java +++ b/multiuser/keycloak/che-multiuser-keycloak-server/src/test/java/org/eclipse/che/multiuser/keycloak/server/KeycloakSettingsTest.java @@ -11,13 +11,262 @@ */ package org.eclipse.che.multiuser.keycloak.server; +import static org.eclipse.che.multiuser.keycloak.server.KeycloakSettings.DEFAULT_USERNAME_CLAIM; +import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.AUTH_SERVER_URL_SETTING; +import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.CLIENT_ID_SETTING; +import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.FIXED_REDIRECT_URL_FOR_DASHBOARD; +import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.FIXED_REDIRECT_URL_FOR_IDE; +import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.GITHUB_ENDPOINT_SETTING; +import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.JS_ADAPTER_URL_SETTING; +import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.JWKS_ENDPOINT_SETTING; +import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.LOGOUT_ENDPOINT_SETTING; +import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.OIDC_PROVIDER_SETTING; +import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.OSO_ENDPOINT_SETTING; +import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.PASSWORD_ENDPOINT_SETTING; +import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.PROFILE_ENDPOINT_SETTING; +import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.REALM_SETTING; +import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.TOKEN_ENDPOINT_SETTING; +import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.USERINFO_ENDPOINT_SETTING; +import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.USERNAME_CLAIM_SETTING; +import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.USE_NONCE_SETTING; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; + +import java.util.Map; +import org.mockito.Mock; +import org.mockito.testng.MockitoTestNGListener; +import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** @author Ilya Buziuk */ +@Listeners(value = {MockitoTestNGListener.class}) public class KeycloakSettingsTest { - @Test(expectedExceptions = RuntimeException.class) - public void shouldNotThrowNPE() { - new KeycloakSettings(null, null, null, null, null, null, null, null, false, null, null, false); + @Mock private OIDCInfo oidcInfo; + + private static final String CHE_REALM = "che"; + private static final String CLIENT_ID = "che-public"; + private static final String PROFILE_URL_PATH = "/realms/" + CHE_REALM + "/account"; + private static final String LOGOUT_URL_PATH = + "/realms/" + CHE_REALM + "/protocol/openid-connect/logout"; + private static final String TOKEN_URL_PATH = + "/realms/" + CHE_REALM + "/protocol/openid-connect/token"; + private static final String USER_INFO_PATH = + "/realms/" + CHE_REALM + "/protocol/openid-connect/userinfo"; + private static final String PASSWORD_URL_PATH = "/realms/" + CHE_REALM + "/account/password"; + private static final String JWKS_ENDPOINT_PATH = + "/realms/" + CHE_REALM + "/protocol/openid-connect/certs"; + private static final String cheServerEndpoint = "https://test-crc-cluster.com.testing"; + + @Test + public void shouldBeSetGithubEndpointSettings() { + final String SERVER_AUTH_URL = "keycloak-che.apps-crc.testing/auth"; + final String GITHUB_ENDPOINT = "github.com/some/endpoint"; + + KeycloakSettings settings = + new KeycloakSettings( + null, + SERVER_AUTH_URL, + null, + CHE_REALM, + CLIENT_ID, + null, + null, + false, + null, + GITHUB_ENDPOINT, + false, + oidcInfo); + + assertEquals(settings.get().get(GITHUB_ENDPOINT_SETTING), GITHUB_ENDPOINT); + } + + @Test + public void shouldBeSetOSOEndpointSettings() { + final String SERVER_AUTH_URL = "https://keycloak-che.apps-crc.testing/auth"; + final String OSO_ENDPOINT = "oso/some/endpoint"; + + KeycloakSettings settings = + new KeycloakSettings( + null, + SERVER_AUTH_URL, + null, + CHE_REALM, + CLIENT_ID, + null, + null, + false, + OSO_ENDPOINT, + null, + false, + oidcInfo); + + assertEquals(settings.get().get(OSO_ENDPOINT_SETTING), OSO_ENDPOINT); + } + + @Test + public void shouldBeEnabledNonce() { + final String SERVER_AUTH_URL = "https://keycloak-che.apps-crc.testing/auth"; + final boolean USE_NONCE = true; + + KeycloakSettings settings = + new KeycloakSettings( + null, + SERVER_AUTH_URL, + null, + CHE_REALM, + CLIENT_ID, + null, + null, + USE_NONCE, + null, + null, + false, + oidcInfo); + + assertEquals(settings.get().get(USE_NONCE_SETTING), "true"); + } + + @Test + public void shouldConfigureKeycloakAdaptersUrl() { + final String SERVER_AUTH_URL = "https://external-keycloak-che.apps-crc.testing/auth"; + final String JS_ADAPTER_URL = "https://js/adapters/endpoint"; + + KeycloakSettings settings = + new KeycloakSettings( + cheServerEndpoint, + JS_ADAPTER_URL, + null, + CHE_REALM, + CLIENT_ID, + SERVER_AUTH_URL, + null, + false, + null, + null, + false, + oidcInfo); + + assertEquals(settings.get().get(JS_ADAPTER_URL_SETTING), JS_ADAPTER_URL); + } + + @Test + public void shouldBeUsedConfigurationFromExternalOIDCProviderWithFixedRedirectLinks() { + final String SERVER_AUTH_URL = "https://external-keycloak-che.apps-crc.testing/auth"; + + KeycloakSettings settings = + new KeycloakSettings( + cheServerEndpoint, + null, + null, + CHE_REALM, + CLIENT_ID, + SERVER_AUTH_URL, + null, + false, + null, + null, + true, + oidcInfo); + + Map publicSettings = settings.get(); + assertEquals(publicSettings.get(OIDC_PROVIDER_SETTING), SERVER_AUTH_URL); + assertEquals( + publicSettings.get(FIXED_REDIRECT_URL_FOR_DASHBOARD), + cheServerEndpoint + "/keycloak/oidcCallbackDashboard.html"); + assertEquals( + publicSettings.get(FIXED_REDIRECT_URL_FOR_IDE), + cheServerEndpoint + "/keycloak/oidcCallbackIde.html"); + assertEquals(publicSettings.get(JS_ADAPTER_URL_SETTING), "/api/keycloak/OIDCKeycloak.js"); + } + + @Test + public void shouldBeUsedConfigurationFromExternalOIDCProviderWithoutFixedRedirectLinks() { + final String SERVER_AUTH_URL = "https://external-keycloak-che.apps-crc.testing/auth"; + + when(oidcInfo.getEndSessionPublicEndpoint()).thenReturn(SERVER_AUTH_URL + LOGOUT_URL_PATH); + when(oidcInfo.getJwksPublicUri()).thenReturn(SERVER_AUTH_URL + JWKS_ENDPOINT_PATH); + when(oidcInfo.getUserInfoPublicEndpoint()).thenReturn(SERVER_AUTH_URL + USER_INFO_PATH); + when(oidcInfo.getTokenPublicEndpoint()).thenReturn(SERVER_AUTH_URL + TOKEN_URL_PATH); + + KeycloakSettings settings = + new KeycloakSettings( + cheServerEndpoint, + null, + null, + CHE_REALM, + CLIENT_ID, + SERVER_AUTH_URL, + null, + false, + null, + null, + false, + oidcInfo); + + Map publicSettings = settings.get(); + assertEquals(publicSettings.get(USERNAME_CLAIM_SETTING), DEFAULT_USERNAME_CLAIM); + assertEquals(publicSettings.get(CLIENT_ID_SETTING), CLIENT_ID); + assertEquals(publicSettings.get(REALM_SETTING), CHE_REALM); + assertNull(publicSettings.get(AUTH_SERVER_URL_SETTING)); + assertNull(publicSettings.get(PROFILE_ENDPOINT_SETTING)); + assertNull(publicSettings.get(PASSWORD_ENDPOINT_SETTING)); + assertEquals(publicSettings.get(LOGOUT_ENDPOINT_SETTING), SERVER_AUTH_URL + LOGOUT_URL_PATH); + assertEquals(publicSettings.get(TOKEN_ENDPOINT_SETTING), SERVER_AUTH_URL + TOKEN_URL_PATH); + assertEquals(publicSettings.get(USERINFO_ENDPOINT_SETTING), SERVER_AUTH_URL + USER_INFO_PATH); + assertEquals(publicSettings.get(JWKS_ENDPOINT_SETTING), SERVER_AUTH_URL + JWKS_ENDPOINT_PATH); + assertNull(publicSettings.get(OSO_ENDPOINT_SETTING)); + assertNull(publicSettings.get(GITHUB_ENDPOINT_SETTING)); + assertEquals(publicSettings.get(OIDC_PROVIDER_SETTING), SERVER_AUTH_URL); + assertNull(publicSettings.get(FIXED_REDIRECT_URL_FOR_DASHBOARD)); + assertNull(publicSettings.get(FIXED_REDIRECT_URL_FOR_IDE)); + assertEquals(publicSettings.get(USE_NONCE_SETTING), "false"); + assertEquals(publicSettings.get(JS_ADAPTER_URL_SETTING), "/api/keycloak/OIDCKeycloak.js"); + } + + @Test + public void shouldBeUsedConfigurationFromExternalAuthServer() { + final String SERVER_AUTH_URL = "https://keycloak-che.apps-crc.testing/auth"; + + when(oidcInfo.getEndSessionPublicEndpoint()).thenReturn(SERVER_AUTH_URL + LOGOUT_URL_PATH); + when(oidcInfo.getJwksPublicUri()).thenReturn(SERVER_AUTH_URL + JWKS_ENDPOINT_PATH); + when(oidcInfo.getUserInfoPublicEndpoint()).thenReturn(SERVER_AUTH_URL + USER_INFO_PATH); + when(oidcInfo.getTokenPublicEndpoint()).thenReturn(SERVER_AUTH_URL + TOKEN_URL_PATH); + + KeycloakSettings settings = + new KeycloakSettings( + null, + null, + SERVER_AUTH_URL, + CHE_REALM, + CLIENT_ID, + null, + null, + false, + null, + null, + false, + oidcInfo); + + Map publicSettings = settings.get(); + assertEquals(publicSettings.get(USERNAME_CLAIM_SETTING), DEFAULT_USERNAME_CLAIM); + assertEquals(publicSettings.get(CLIENT_ID_SETTING), CLIENT_ID); + assertEquals(publicSettings.get(REALM_SETTING), CHE_REALM); + assertEquals(publicSettings.get(AUTH_SERVER_URL_SETTING), SERVER_AUTH_URL); + assertEquals(publicSettings.get(PROFILE_ENDPOINT_SETTING), SERVER_AUTH_URL + PROFILE_URL_PATH); + assertEquals( + publicSettings.get(PASSWORD_ENDPOINT_SETTING), SERVER_AUTH_URL + PASSWORD_URL_PATH); + assertEquals(publicSettings.get(LOGOUT_ENDPOINT_SETTING), SERVER_AUTH_URL + LOGOUT_URL_PATH); + assertEquals(publicSettings.get(TOKEN_ENDPOINT_SETTING), SERVER_AUTH_URL + TOKEN_URL_PATH); + assertEquals(publicSettings.get(USERINFO_ENDPOINT_SETTING), SERVER_AUTH_URL + USER_INFO_PATH); + assertEquals(publicSettings.get(JWKS_ENDPOINT_SETTING), SERVER_AUTH_URL + JWKS_ENDPOINT_PATH); + assertNull(publicSettings.get(OSO_ENDPOINT_SETTING)); + assertNull(publicSettings.get(GITHUB_ENDPOINT_SETTING)); + assertNull(publicSettings.get(OIDC_PROVIDER_SETTING)); + assertNull(publicSettings.get(FIXED_REDIRECT_URL_FOR_DASHBOARD)); + assertNull(publicSettings.get(FIXED_REDIRECT_URL_FOR_IDE)); + assertEquals(publicSettings.get(USE_NONCE_SETTING), "false"); + assertEquals(publicSettings.get(JS_ADAPTER_URL_SETTING), SERVER_AUTH_URL + "/js/keycloak.js"); } } diff --git a/multiuser/keycloak/che-multiuser-keycloak-server/src/test/java/org/eclipse/che/multiuser/keycloak/server/OIDCInfoProviderTest.java b/multiuser/keycloak/che-multiuser-keycloak-server/src/test/java/org/eclipse/che/multiuser/keycloak/server/OIDCInfoProviderTest.java new file mode 100644 index 00000000000..c0af1401add --- /dev/null +++ b/multiuser/keycloak/che-multiuser-keycloak-server/src/test/java/org/eclipse/che/multiuser/keycloak/server/OIDCInfoProviderTest.java @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.keycloak.server; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.testng.Assert.assertEquals; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +public class OIDCInfoProviderTest { + private WireMockServer wireMockServer; + + private static final String CHE_REALM = "che"; + private static final String TEST_URL = "some-test-url-to-skip"; + private static final String SERVER_URL = "http://localhost:" + getHttpPort() + "/auth"; + private static final String OPEN_ID_CONF_TEMPLATE = + "" + + "{" + + " \"token_endpoint\": \"" + + SERVER_URL + + "/realms/" + + CHE_REALM + + "/protocol/openid-connect/token\"," + + " \"end_session_endpoint\": \"" + + SERVER_URL + + "/realms/" + + CHE_REALM + + "/protocol/openid-connect/logout\"," + + " \"userinfo_endpoint\": \"" + + SERVER_URL + + "/realms/" + + CHE_REALM + + "/protocol/openid-connect/userinfo\"," + + " \"jwks_uri\": \"" + + SERVER_URL + + "/realms/" + + CHE_REALM + + "/protocol/openid-connect/certs\"" + + "}"; + + @BeforeClass + void start() { + wireMockServer = new WireMockServer(wireMockConfig().port(getHttpPort())); + wireMockServer.start(); + WireMock.configureFor("localhost", getHttpPort()); + } + + @AfterClass + void stop() { + if (wireMockServer != null) { + wireMockServer.stop(); + } + } + + @Test( + expectedExceptions = RuntimeException.class, + expectedExceptionsMessageRegExp = + "Exception while retrieving OpenId configuration from endpoint: .*") + public void shouldFailToParseOIDCConfiguration() { + stubFor( + get(urlEqualTo("/auth/realms/che/.well-known/openid-configuration")) + .willReturn( + aResponse().withHeader("Content-Type", "text/html").withBody("broken json"))); + + OIDCInfoProvider oidcInfoProvider = new OIDCInfoProvider(); + oidcInfoProvider.oidcProviderUrl = SERVER_URL; + oidcInfoProvider.realm = CHE_REALM; + + oidcInfoProvider.get(); + } + + @Test + public void shouldParseOIDCConfigurationForServerUrl() { + stubFor( + get(urlEqualTo("/auth/realms/che/.well-known/openid-configuration")) + .willReturn( + aResponse() + .withHeader("Content-Type", "text/html") + .withBody(OPEN_ID_CONF_TEMPLATE))); + + OIDCInfoProvider oidcInfoProvider = new OIDCInfoProvider(); + oidcInfoProvider.serverURL = SERVER_URL; + oidcInfoProvider.realm = CHE_REALM; + OIDCInfo oidcInfo = oidcInfoProvider.get(); + + assertEquals( + SERVER_URL + "/realms/" + CHE_REALM + "/protocol/openid-connect/token", + oidcInfo.getTokenPublicEndpoint()); + assertEquals( + SERVER_URL + "/realms/" + CHE_REALM + "/protocol/openid-connect/logout", + oidcInfo.getEndSessionPublicEndpoint()); + assertEquals( + SERVER_URL + "/realms/" + CHE_REALM + "/protocol/openid-connect/userinfo", + oidcInfo.getUserInfoEndpoint()); + assertEquals( + SERVER_URL + "/realms/" + CHE_REALM + "/protocol/openid-connect/certs", + oidcInfo.getJwksUri()); + } + + @Test + public void shouldParseOIDCConfigurationForInternalServerUrl() { + String serverPublicUrl = "che-eclipse-che.apps-crc.testing"; + String OPEN_ID_CONF_TEMPLATE = + "" + + "{" + + " \"token_endpoint\": \"" + + serverPublicUrl + + "/realms/" + + CHE_REALM + + "/protocol/openid-connect/token\"," + + " \"end_session_endpoint\": \"" + + serverPublicUrl + + "/realms/" + + CHE_REALM + + "/protocol/openid-connect/logout\"," + + " \"userinfo_endpoint\": \"" + + serverPublicUrl + + "/realms/" + + CHE_REALM + + "/protocol/openid-connect/userinfo\"," + + " \"jwks_uri\": \"" + + serverPublicUrl + + "/realms/" + + CHE_REALM + + "/protocol/openid-connect/certs\"" + + "}"; + + stubFor( + get(urlEqualTo("/auth/realms/che/.well-known/openid-configuration")) + .willReturn( + aResponse() + .withHeader("Content-Type", "text/html") + .withBody(OPEN_ID_CONF_TEMPLATE))); + + OIDCInfoProvider oidcInfoProvider = new OIDCInfoProvider(); + oidcInfoProvider.serverURL = serverPublicUrl; + oidcInfoProvider.serverInternalURL = SERVER_URL; + oidcInfoProvider.realm = CHE_REALM; + OIDCInfo oidcInfo = oidcInfoProvider.get(); + + assertEquals( + serverPublicUrl + "/realms/" + CHE_REALM + "/protocol/openid-connect/token", + oidcInfo.getTokenPublicEndpoint()); + assertEquals( + serverPublicUrl + "/realms/" + CHE_REALM + "/protocol/openid-connect/logout", + oidcInfo.getEndSessionPublicEndpoint()); + assertEquals( + serverPublicUrl + "/realms/" + CHE_REALM + "/protocol/openid-connect/userinfo", + oidcInfo.getUserInfoPublicEndpoint()); + assertEquals( + serverPublicUrl + "/realms/" + CHE_REALM + "/protocol/openid-connect/certs", + oidcInfo.getJwksPublicUri()); + + assertEquals( + SERVER_URL + "/realms/" + CHE_REALM + "/protocol/openid-connect/certs", + oidcInfo.getJwksUri()); + assertEquals( + SERVER_URL + "/realms/" + CHE_REALM + "/protocol/openid-connect/userinfo", + oidcInfo.getUserInfoEndpoint()); + assertEquals(SERVER_URL, oidcInfo.getAuthServerURL()); + } + + @Test + public void shouldParseOIDCConfigurationForOIDCProviderUrl() { + String OIDCProviderUrl = "http://localhost:" + getHttpPort() + "/realms/"; + stubFor( + get(urlEqualTo("/realms/.well-known/openid-configuration")) + .willReturn( + aResponse() + .withHeader("Content-Type", "text/html") + .withBody(OPEN_ID_CONF_TEMPLATE))); + + OIDCInfoProvider oidcInfoProvider = new OIDCInfoProvider(); + oidcInfoProvider.serverURL = TEST_URL; + oidcInfoProvider.serverInternalURL = TEST_URL; + oidcInfoProvider.oidcProviderUrl = OIDCProviderUrl; + oidcInfoProvider.realm = CHE_REALM; + OIDCInfo oidcInfo = oidcInfoProvider.get(); + + assertEquals( + SERVER_URL + "/realms/" + CHE_REALM + "/protocol/openid-connect/token", + oidcInfo.getTokenPublicEndpoint()); + assertEquals( + SERVER_URL + "/realms/" + CHE_REALM + "/protocol/openid-connect/logout", + oidcInfo.getEndSessionPublicEndpoint()); + assertEquals( + SERVER_URL + "/realms/" + CHE_REALM + "/protocol/openid-connect/userinfo", + oidcInfo.getUserInfoEndpoint()); + assertEquals( + SERVER_URL + "/realms/" + CHE_REALM + "/protocol/openid-connect/certs", + oidcInfo.getJwksUri()); + } + + @Test( + expectedExceptions = RuntimeException.class, + expectedExceptionsMessageRegExp = "Either the '.*' or '.*' or '.*' property should be set") + public void shouldThrowErrorWhenAuthServerWasNotSet() { + OIDCInfoProvider oidcInfoProvider = new OIDCInfoProvider(); + oidcInfoProvider.realm = CHE_REALM; + oidcInfoProvider.get(); + } + + @Test( + expectedExceptions = RuntimeException.class, + expectedExceptionsMessageRegExp = "The '.*' property should be set") + public void shouldThrowErrorWhenRealmPropertyWasNotSet() { + OIDCInfoProvider oidcInfoProvider = new OIDCInfoProvider(); + oidcInfoProvider.serverURL = TEST_URL; + oidcInfoProvider.get(); + } + + private static int getHttpPort() { + return 3001; + } +} diff --git a/multiuser/keycloak/che-multiuser-keycloak-user-remover/src/main/java/org/eclipse/che/multiuser/keycloak/server/KeycloakUserRemover.java b/multiuser/keycloak/che-multiuser-keycloak-user-remover/src/main/java/org/eclipse/che/multiuser/keycloak/server/KeycloakUserRemover.java index c34386de526..688b96c4e30 100644 --- a/multiuser/keycloak/che-multiuser-keycloak-user-remover/src/main/java/org/eclipse/che/multiuser/keycloak/server/KeycloakUserRemover.java +++ b/multiuser/keycloak/che-multiuser-keycloak-user-remover/src/main/java/org/eclipse/che/multiuser/keycloak/server/KeycloakUserRemover.java @@ -70,13 +70,14 @@ public KeycloakUserRemover( @Nullable @Named("che.keycloak.admin_username") String keycloakUser, @Nullable @Named("che.keycloak.admin_password") String keycloakPassword, KeycloakSettings keycloakSettings, + OIDCInfo oidcInfo, HttpJsonRequestFactory requestFactory) { this.keycloakUser = keycloakUser; this.keycloakPassword = keycloakPassword; this.requestFactory = requestFactory; + if (userRemovalEnabled) { - String serverUrl = - keycloakSettings.getInternalSettings().get(AUTH_SERVER_URL_INTERNAL_SETTING); + String serverUrl = oidcInfo.getAuthServerURL(); if (serverUrl == null) { throw new ConfigurationException( AUTH_SERVER_URL_SETTING diff --git a/pom.xml b/pom.xml index 40d18ae7e92..0fe4f14ae2f 100644 --- a/pom.xml +++ b/pom.xml @@ -51,6 +51,7 @@ 0.3.0 2.10.3 1.7.1 + 2.27.2 4.2.2 27.0.1-jre 1.27.0 @@ -162,6 +163,11 @@ jackson-dataformat-yaml ${com.fasterxml.jackson.core.version} + + com.github.tomakehurst + wiremock-jre8-standalone + ${com.github.tomakehurst.wiremock-jre8-standalone.version} + com.google.code.findbugs jsr305