keycloak-aplcache
Changes
services/src/main/java/org/keycloak/social/openshift/OpenshiftV3IdentityProviderFactory.java 27(+27 -0)
services/src/main/java/org/keycloak/social/openshift/OpenshifV3IdentityProviderConfig.java 27(+27 -0)
services/src/main/resources/META-INF/services/org.keycloak.broker.social.SocialIdentityProviderFactory 1(+1 -0)
services/src/test/java/org/keycloak/social/openshift/OpenshiftV3IdentityProviderTest.java 33(+33 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java 20(+20 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/SocialLoginTest.java 25(+21 -4)
Details
diff --git a/services/src/main/java/org/keycloak/social/openshift/OpenshiftV3IdentityProvider.java b/services/src/main/java/org/keycloak/social/openshift/OpenshiftV3IdentityProvider.java
new file mode 100644
index 0000000..8bc4bd5
--- /dev/null
+++ b/services/src/main/java/org/keycloak/social/openshift/OpenshiftV3IdentityProvider.java
@@ -0,0 +1,66 @@
+package org.keycloak.social.openshift;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
+import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
+import org.keycloak.broker.oidc.util.JsonSimpleHttp;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
+import org.keycloak.broker.provider.IdentityBrokerException;
+import org.keycloak.broker.provider.util.SimpleHttp;
+import org.keycloak.broker.social.SocialIdentityProvider;
+import org.keycloak.models.KeycloakSession;
+
+import java.io.IOException;
+import java.util.Optional;
+
+/**
+ * Identity provider for Openshift V3. Check <a href="https://docs.openshift.com/enterprise/3.0/architecture/additional_concepts/authentication.html">official documentation</a> for more details.
+ */
+public class OpenshiftV3IdentityProvider extends AbstractOAuth2IdentityProvider<OpenshifV3IdentityProviderConfig> implements SocialIdentityProvider<OpenshifV3IdentityProviderConfig> {
+
+ public static final String BASE_URL = "https://api.preview.openshift.com";
+ private static final String AUTH_RESOURCE = "/oauth/authorize";
+ private static final String TOKEN_RESOURCE = "/oauth/token";
+ private static final String PROFILE_RESOURCE = "/oapi/v1/users/~";
+ private static final String DEFAULT_SCOPE = "user:info";
+
+ public OpenshiftV3IdentityProvider(KeycloakSession session, OpenshifV3IdentityProviderConfig config) {
+ super(session, config);
+ final String baseUrl = Optional.ofNullable(config.getBaseUrl()).orElse(BASE_URL);
+ config.setAuthorizationUrl(baseUrl + AUTH_RESOURCE);
+ config.setTokenUrl(baseUrl + TOKEN_RESOURCE);
+ config.setUserInfoUrl(baseUrl + PROFILE_RESOURCE);
+ }
+
+ @Override
+ protected String getDefaultScopes() {
+ return DEFAULT_SCOPE;
+ }
+
+ @Override
+ protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) {
+ try {
+ final JsonNode profile = fetchProfile(accessToken);
+ final BrokeredIdentityContext user = extractUserContext(profile.get("metadata"));
+ AbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile, getConfig().getAlias());
+ return user;
+ } catch (Exception e) {
+ throw new IdentityBrokerException("Could not obtain user profile from Openshift.", e);
+ }
+ }
+
+ private BrokeredIdentityContext extractUserContext(JsonNode metadata) {
+ final BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(metadata, "uid"));
+ user.setUsername(getJsonProperty(metadata, "name"));
+ user.setName(getJsonProperty(metadata, "fullName"));
+ user.setIdpConfig(getConfig());
+ user.setIdp(this);
+ return user;
+ }
+
+ private JsonNode fetchProfile(String accessToken) throws IOException {
+ return JsonSimpleHttp.asJson(SimpleHttp.doGet(getConfig().getUserInfoUrl(), this.session)
+ .header("Authorization", "Bearer " + accessToken));
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/social/openshift/OpenshiftV3IdentityProviderFactory.java b/services/src/main/java/org/keycloak/social/openshift/OpenshiftV3IdentityProviderFactory.java
new file mode 100644
index 0000000..b370530
--- /dev/null
+++ b/services/src/main/java/org/keycloak/social/openshift/OpenshiftV3IdentityProviderFactory.java
@@ -0,0 +1,27 @@
+package org.keycloak.social.openshift;
+
+import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
+import org.keycloak.broker.social.SocialIdentityProviderFactory;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.KeycloakSession;
+
+public class OpenshiftV3IdentityProviderFactory extends AbstractIdentityProviderFactory<OpenshiftV3IdentityProvider> implements SocialIdentityProviderFactory<OpenshiftV3IdentityProvider> {
+
+ public static final String PROVIDER_ID = "openshift-v3";
+
+ @Override
+ public String getName() {
+ return "Openshift v3";
+ }
+
+ @Override
+ public OpenshiftV3IdentityProvider create(KeycloakSession keycloakSession, IdentityProviderModel identityProviderModel) {
+ return new OpenshiftV3IdentityProvider(keycloakSession, new OpenshifV3IdentityProviderConfig(identityProviderModel));
+ }
+
+ @Override
+ public String getId() {
+ return PROVIDER_ID;
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/social/openshift/OpenshifV3IdentityProviderConfig.java b/services/src/main/java/org/keycloak/social/openshift/OpenshifV3IdentityProviderConfig.java
new file mode 100644
index 0000000..cd931f8
--- /dev/null
+++ b/services/src/main/java/org/keycloak/social/openshift/OpenshifV3IdentityProviderConfig.java
@@ -0,0 +1,27 @@
+package org.keycloak.social.openshift;
+
+import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
+import org.keycloak.models.IdentityProviderModel;
+
+public class OpenshifV3IdentityProviderConfig extends OAuth2IdentityProviderConfig {
+ private static final String BASE_URL = "baseUrl";
+
+ public OpenshifV3IdentityProviderConfig(IdentityProviderModel identityProviderModel) {
+ super(identityProviderModel);
+ }
+
+ public String getBaseUrl() {
+ return getConfig().get(BASE_URL);
+ }
+
+ public void setBaseUrl(String baseUrl) {
+ getConfig().put(BASE_URL, trimTrailingSlash(baseUrl));
+ }
+
+ private String trimTrailingSlash(String baseUrl) {
+ if (baseUrl != null && baseUrl.endsWith("/")) {
+ baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
+ }
+ return baseUrl;
+ }
+}
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.broker.social.SocialIdentityProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.broker.social.SocialIdentityProviderFactory
index 1311132..00a5e51 100755
--- a/services/src/main/resources/META-INF/services/org.keycloak.broker.social.SocialIdentityProviderFactory
+++ b/services/src/main/resources/META-INF/services/org.keycloak.broker.social.SocialIdentityProviderFactory
@@ -22,3 +22,4 @@ org.keycloak.social.linkedin.LinkedInIdentityProviderFactory
org.keycloak.social.stackoverflow.StackoverflowIdentityProviderFactory
org.keycloak.social.twitter.TwitterIdentityProviderFactory
org.keycloak.social.microsoft.MicrosoftIdentityProviderFactory
+org.keycloak.social.openshift.OpenshiftV3IdentityProviderFactory
diff --git a/services/src/test/java/org/keycloak/social/openshift/OpenshiftV3IdentityProviderTest.java b/services/src/test/java/org/keycloak/social/openshift/OpenshiftV3IdentityProviderTest.java
new file mode 100644
index 0000000..e39f157
--- /dev/null
+++ b/services/src/test/java/org/keycloak/social/openshift/OpenshiftV3IdentityProviderTest.java
@@ -0,0 +1,33 @@
+package org.keycloak.social.openshift;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.models.IdentityProviderModel;
+
+public class OpenshiftV3IdentityProviderTest {
+
+ @Test
+ public void shouldConstructProviderUrls() throws Exception {
+ final OpenshifV3IdentityProviderConfig config = new OpenshifV3IdentityProviderConfig(new IdentityProviderModel());
+ config.setBaseUrl("http://openshift.io:8443");
+ final OpenshiftV3IdentityProvider openshiftV3IdentityProvider = new OpenshiftV3IdentityProvider(null, config);
+
+ assertConfiguredUrls(openshiftV3IdentityProvider);
+ }
+
+ @Test
+ public void shouldConstructProviderUrlsForBaseUrlWithTrailingSlash() throws Exception {
+ final OpenshifV3IdentityProviderConfig config = new OpenshifV3IdentityProviderConfig(new IdentityProviderModel());
+ config.setBaseUrl("http://openshift.io:8443/");
+ final OpenshiftV3IdentityProvider openshiftV3IdentityProvider = new OpenshiftV3IdentityProvider(null, config);
+
+ assertConfiguredUrls(openshiftV3IdentityProvider);
+ }
+
+ private void assertConfiguredUrls(OpenshiftV3IdentityProvider openshiftV3IdentityProvider) {
+ Assert.assertEquals("http://openshift.io:8443/oauth/authorize", openshiftV3IdentityProvider.getConfig().getAuthorizationUrl());
+ Assert.assertEquals("http://openshift.io:8443/oauth/token", openshiftV3IdentityProvider.getConfig().getTokenUrl());
+ Assert.assertEquals("http://openshift.io:8443/oapi/v1/users/~", openshiftV3IdentityProvider.getConfig().getUserInfoUrl());
+ }
+
+}
\ No newline at end of file
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java
index 09ddb39..1fb0a63 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java
@@ -37,6 +37,9 @@ import org.keycloak.social.google.GoogleIdentityProvider;
import org.keycloak.social.google.GoogleIdentityProviderFactory;
import org.keycloak.social.linkedin.LinkedInIdentityProvider;
import org.keycloak.social.linkedin.LinkedInIdentityProviderFactory;
+import org.keycloak.social.openshift.OpenshifV3IdentityProviderConfig;
+import org.keycloak.social.openshift.OpenshiftV3IdentityProvider;
+import org.keycloak.social.openshift.OpenshiftV3IdentityProviderFactory;
import org.keycloak.social.stackoverflow.StackOverflowIdentityProviderConfig;
import org.keycloak.social.stackoverflow.StackoverflowIdentityProvider;
import org.keycloak.social.stackoverflow.StackoverflowIdentityProviderFactory;
@@ -146,6 +149,8 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
assertLinkedInIdentityProviderConfig(identityProvider);
} else if (StackoverflowIdentityProviderFactory.PROVIDER_ID.equals(providerId)) {
assertStackoverflowIdentityProviderConfig(identityProvider);
+ } else if (OpenshiftV3IdentityProviderFactory.PROVIDER_ID.equals(providerId)) {
+ assertOpenshiftIdentityProviderConfig(identityProvider);
} else {
continue;
}
@@ -283,6 +288,21 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
assertEquals(StackoverflowIdentityProvider.PROFILE_URL, config.getUserInfoUrl());
}
+ private void assertOpenshiftIdentityProviderConfig(IdentityProviderModel identityProvider) {
+ OpenshiftV3IdentityProvider osoIdentityProvider = new OpenshiftV3IdentityProviderFactory().create(session, identityProvider);
+ OpenshifV3IdentityProviderConfig config = osoIdentityProvider.getConfig();
+
+ assertEquals("model-openshift-v3", config.getAlias());
+ assertEquals(OpenshiftV3IdentityProviderFactory.PROVIDER_ID, config.getProviderId());
+ assertEquals(true, config.isEnabled());
+ assertEquals(false, config.isTrustEmail());
+ assertEquals(false, config.isAuthenticateByDefault());
+ assertEquals(true, config.isStoreToken());
+ assertEquals(OpenshiftV3IdentityProvider.BASE_URL, config.getBaseUrl());
+ assertEquals("clientId", config.getClientId());
+ assertEquals("clientSecret", config.getClientSecret());
+ }
+
private void assertTwitterIdentityProviderConfig(IdentityProviderModel identityProvider) {
TwitterIdentityProvider twitterIdentityProvider = new TwitterIdentityProviderFactory().create(session, identityProvider);
OAuth2IdentityProviderConfig config = twitterIdentityProvider.getConfig();
diff --git a/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json b/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json
index 1bfc295..09fa373 100755
--- a/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json
+++ b/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json
@@ -92,6 +92,20 @@
}
},
{
+ "alias" : "model-openshift-v3",
+ "providerId" : "openshift-v3",
+ "enabled": true,
+ "storeToken": true,
+ "config": {
+ "baseUrl": "https://api.preview.openshift.com",
+ "authorizationUrl": "authorizationUrl",
+ "tokenUrl": "tokenUrl",
+ "userInfoUrl": "userInfoUrl",
+ "clientId": "clientId",
+ "clientSecret": "clientSecret"
+ }
+ },
+ {
"alias" : "model-saml-signed-idp",
"providerId" : "saml",
"displayName": "My SAML",
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/SocialLoginTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/SocialLoginTest.java
index 6ac3970..46c856e 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/SocialLoginTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/SocialLoginTest.java
@@ -2,15 +2,12 @@ package org.keycloak.testsuite.broker;
import org.jboss.arquillian.graphene.Graphene;
import org.jboss.arquillian.graphene.page.Page;
-import org.jboss.arquillian.graphene.wait.WebDriverWait;
-import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Test;
-import org.keycloak.common.Profile;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.social.openshift.OpenshiftV3IdentityProvider;
import org.keycloak.testsuite.AbstractKeycloakTest;
-import org.keycloak.testsuite.cli.exec.ExecutionException;
import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.LoginUpdateProfilePage;
@@ -59,6 +56,7 @@ public class SocialLoginTest extends AbstractKeycloakTest {
List<IdentityProviderRepresentation> idps = new LinkedList<>();
rep.setIdentityProviders(idps);
+ idps.add(buildIdp("openshift-v3"));
idps.add(buildIdp("google"));
idps.add(buildIdp("facebook"));
idps.add(buildIdp("github"));
@@ -71,6 +69,22 @@ public class SocialLoginTest extends AbstractKeycloakTest {
}
@Test
+ public void openshiftLogin() throws Exception {
+ account.open("social");
+ loginPage.clickSocial("openshift-v3");
+
+ Graphene.waitGui().until(ExpectedConditions.visibilityOfElementLocated(By.id("inputUsername")));
+ driver.findElement(By.id("inputUsername")).sendKeys(config.getProperty("openshift-v3.username", config.getProperty("common.username")));
+ driver.findElement(By.id("inputPassword")).sendKeys(config.getProperty("openshift-v3.password", config.getProperty("common.password")));
+ driver.findElement(By.cssSelector("button[type=submit]")).click();
+
+ Graphene.waitGui().until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("input[name=approve]")));
+ driver.findElement(By.cssSelector("input[name=approve]")).click();
+
+ assertEquals(config.getProperty("openshift-v3.username", config.getProperty("common.profile.username")), account.getUsername());
+ }
+
+ @Test
public void googleLogin() throws InterruptedException {
account.open("social");
@@ -226,6 +240,9 @@ public class SocialLoginTest extends AbstractKeycloakTest {
if (id.equals("stackoverflow")) {
idp.getConfig().put("key", config.getProperty(id + ".clientKey"));
}
+ if (id.equals("openshift-v3")) {
+ idp.getConfig().put("baseUrl", config.getProperty(id + ".baseUrl", OpenshiftV3IdentityProvider.BASE_URL));
+ }
return idp;
}
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index 0859504..97b2808 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -547,6 +547,8 @@ social.client-secret.tooltip=The client secret registered with the identity prov
social.default-scopes.tooltip=The scopes to be sent when asking for authorization. See documentation for possible values, separator and default value'.
key=Key
stackoverflow.key.tooltip=The Key obtained from Stack Overflow client registration.
+openshift.base-url=Base Url
+openshift.base-url.tooltip=Base Url to Openshift Online API
# User federation
sync-ldap-roles-to-keycloak=Sync LDAP Roles To Keycloak
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-openshift-v3.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-openshift-v3.html
new file mode 100755
index 0000000..a4630ac
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-openshift-v3.html
@@ -0,0 +1 @@
+<div data-ng-include data-src="resourceUrl + '/partials/realm-identity-provider-social.html'"></div>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-openshift-v3-ext.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-openshift-v3-ext.html
new file mode 100644
index 0000000..b1c27de
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-openshift-v3-ext.html
@@ -0,0 +1,7 @@
+<div class="form-group clearfix">
+ <label class="col-md-2 control-label" for="baseUrl"><span class="required">*</span> {{:: 'openshift.base-url' | translate}}</label>
+ <div class="col-md-6">
+ <input class="form-control" id="baseUrl" type="text" ng-model="identityProvider.config.baseUrl" required>
+ </div>
+ <kc-tooltip>{{:: 'openshift.base-url.tooltip' | translate}}</kc-tooltip>
+</div>