diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/util/SimpleHttp.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/util/SimpleHttp.java
index 870e951a96..c9050b8296 100644
--- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/util/SimpleHttp.java
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/util/SimpleHttp.java
@@ -13,9 +13,11 @@ import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
+import java.util.zip.GZIPInputStream;
/**
* @author Stian Thorgersen
+ * @author Vlastimil Elias (velias at redhat dot com)
*/
public class SimpleHttp {
@@ -116,7 +118,11 @@ public class SimpleHttp {
connection.setDoOutput(false);
}
+ String ce = connection.getHeaderField("Content-Encoding");
is = connection.getInputStream();
+ if ("gzip".equals(ce)) {
+ is = new GZIPInputStream(is);
+ }
return toString(is);
} finally {
if (os != null) {
diff --git a/dependencies/server-all/pom.xml b/dependencies/server-all/pom.xml
index a7bd5223bb..2c1f6f350f 100755
--- a/dependencies/server-all/pom.xml
+++ b/dependencies/server-all/pom.xml
@@ -127,6 +127,11 @@
keycloak-social-linkedin${project.version}
+
+ org.keycloak
+ keycloak-social-stackoverflow
+ ${project.version}
+
diff --git a/distribution/modules/build.xml b/distribution/modules/build.xml
index ad0f9e7c11..80ff507230 100755
--- a/distribution/modules/build.xml
+++ b/distribution/modules/build.xml
@@ -239,6 +239,10 @@
+
+
+
+
diff --git a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-server/main/module.xml b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-server/main/module.xml
index f8a251d7a5..f553d24263 100755
--- a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-server/main/module.xml
+++ b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-server/main/module.xml
@@ -58,6 +58,7 @@
+
diff --git a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml
index 6166b9b6ac..86e86f492e 100755
--- a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml
+++ b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml
@@ -61,6 +61,7 @@
+
diff --git a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-social-stackoverflow/main/module.xml b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-social-stackoverflow/main/module.xml
new file mode 100755
index 0000000000..6ddd2a4b93
--- /dev/null
+++ b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-social-stackoverflow/main/module.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/distribution/subsystem-war/src/main/webapp/WEB-INF/jboss-deployment-structure.xml b/distribution/subsystem-war/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
index 6a97ca5866..6caa2c81e5 100755
--- a/distribution/subsystem-war/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
+++ b/distribution/subsystem-war/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
@@ -51,6 +51,7 @@
+
diff --git a/docbook/reference/en/en-US/modules/identity-broker.xml b/docbook/reference/en/en-US/modules/identity-broker.xml
index 3a74b734d8..5ebbbe239f 100755
--- a/docbook/reference/en/en-US/modules/identity-broker.xml
+++ b/docbook/reference/en/en-US/modules/identity-broker.xml
@@ -807,6 +807,78 @@
+
+
+ StackOverflow
+
+ To enable login with StackOverflow you first have to register an OAuth application on
+ StackApps. Then you need to copy the client id, secret and key into the Keycloak Admin Console.
+
+
+ Let's see first how to create an application with StackOverflow.
+
+
+
+
+ Go to registering your application on Stack Apps url and login here.
+ Use any value for Application Name, Application Website and Description you want.
+ Set OAuth Domain to the domain where your Keycloak instance runs.
+ Click the Register Your Application button.
+
+
+
+
+ Copy Client Id, Client Secret and Key from the shown page.
+
+
+
+
+ Now that you have the client id, secret and key, you can proceed with the creation of a StackOverflow Identity Provider in Keycloak. As follows:
+
+
+
+
+ Select the StackOverflow identity provider from the drop-down box on the top right corner of the identity providers table in Keycloak's Admin Console. You should be presented with a specific page to configure the selected provided.
+
+
+
+
+ Copy the client id, client secret and key to their corresponding fields in the Keycloak Admin Console. Click Save.
+
+
+
+
+ That is it! This pretty much what you need to do in order to setup this identity provider.
+
+
+ The table below lists some additional configuration options you may use when configuring this provider.
+
+
+ Configuration Options
+
+
+
+
+ Configuration
+
+
+ Description
+
+
+
+
+
+
+ Default Scopes
+
+
+ Allows you to manually specify the scopes that users must authorize when authenticating with this provider.
+ For a complete list of scopes, please take a look at application configuration in StackExchange API Authentication documentation. Keycloak uses the empty scope by default.
+
+
+
+
+
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-facebook-ext.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-facebook-ext.html
new file mode 100755
index 0000000000..e69de29bb2
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-github-ext.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-github-ext.html
new file mode 100755
index 0000000000..e69de29bb2
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-google-ext.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-google-ext.html
new file mode 100755
index 0000000000..e69de29bb2
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-linkedin-ext.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-linkedin-ext.html
new file mode 100755
index 0000000000..e69de29bb2
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-linkedin.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-linkedin.html
similarity index 100%
rename from forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-linkedin.html
rename to forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-linkedin.html
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html
index aa6a5ebe98..4a23349a38 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html
@@ -28,12 +28,13 @@
+
-
+
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-stackoverflow-ext.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-stackoverflow-ext.html
new file mode 100755
index 0000000000..86516dfaaa
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-stackoverflow-ext.html
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-stackoverflow.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-stackoverflow.html
new file mode 100755
index 0000000000..a4630ac786
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-stackoverflow.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-twitter-ext.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-twitter-ext.html
new file mode 100755
index 0000000000..e69de29bb2
diff --git a/forms/common-themes/src/main/resources/theme/patternfly/login/resources/css/login.css b/forms/common-themes/src/main/resources/theme/patternfly/login/resources/css/login.css
index 345f594a06..2bc07bb274 100644
--- a/forms/common-themes/src/main/resources/theme/patternfly/login/resources/css/login.css
+++ b/forms/common-themes/src/main/resources/theme/patternfly/login/resources/css/login.css
@@ -238,7 +238,7 @@ ol#kc-totp-settings li:first-of-type {
}
.zocial {
- width: 125px;
+ width: 150px;
}
.zocial:hover {
diff --git a/social/pom.xml b/social/pom.xml
index ded7c60eed..ec08ca2309 100755
--- a/social/pom.xml
+++ b/social/pom.xml
@@ -21,6 +21,7 @@
twitterfacebooklinkedin
+ stackoverflow
diff --git a/social/stackoverflow/.gitignore b/social/stackoverflow/.gitignore
new file mode 100644
index 0000000000..b83d22266a
--- /dev/null
+++ b/social/stackoverflow/.gitignore
@@ -0,0 +1 @@
+/target/
diff --git a/social/stackoverflow/pom.xml b/social/stackoverflow/pom.xml
new file mode 100755
index 0000000000..f05a77b563
--- /dev/null
+++ b/social/stackoverflow/pom.xml
@@ -0,0 +1,40 @@
+
+
+ keycloak-social-parent
+ org.keycloak
+ 1.2.0.Beta1-SNAPSHOT
+ ../pom.xml
+
+ 4.0.0
+ jar
+
+ keycloak-social-stackoverflow
+ Keycloak Social StackOverflow
+
+
+
+
+ org.keycloak
+ keycloak-social-core
+ ${project.version}
+ provided
+
+
+ org.keycloak
+ keycloak-broker-oidc
+ ${project.version}
+ provided
+
+
+ org.codehaus.jackson
+ jackson-mapper-asl
+ provided
+
+
+ org.jboss.logging
+ jboss-logging
+ provided
+
+
+
diff --git a/social/stackoverflow/src/main/java/org/keycloak/social/stackoverflow/StackOverflowIdentityProviderConfig.java b/social/stackoverflow/src/main/java/org/keycloak/social/stackoverflow/StackOverflowIdentityProviderConfig.java
new file mode 100644
index 0000000000..f531d7c71d
--- /dev/null
+++ b/social/stackoverflow/src/main/java/org/keycloak/social/stackoverflow/StackOverflowIdentityProviderConfig.java
@@ -0,0 +1,40 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Copyright 2013 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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.keycloak.social.stackoverflow;
+
+import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
+import org.keycloak.models.IdentityProviderModel;
+
+/**
+ * @author Vlastimil Elias (velias at redhat dot com)
+ */
+public class StackOverflowIdentityProviderConfig extends OAuth2IdentityProviderConfig {
+
+ public StackOverflowIdentityProviderConfig(IdentityProviderModel model) {
+ super(model);
+ }
+
+ public String getKey() {
+ return getConfig().get("key");
+ }
+
+ public void setKey(String key) {
+ getConfig().put("key", key);
+ }
+
+}
\ No newline at end of file
diff --git a/social/stackoverflow/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProvider.java b/social/stackoverflow/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProvider.java
new file mode 100755
index 0000000000..40fb32f2cd
--- /dev/null
+++ b/social/stackoverflow/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProvider.java
@@ -0,0 +1,214 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Copyright 2015 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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.keycloak.social.stackoverflow;
+
+import java.io.StringWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.util.HashMap;
+
+import org.codehaus.jackson.JsonNode;
+import org.jboss.logging.Logger;
+import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
+import org.keycloak.broker.oidc.util.SimpleHttp;
+import org.keycloak.broker.provider.FederatedIdentity;
+import org.keycloak.broker.provider.IdentityBrokerException;
+import org.keycloak.social.SocialIdentityProvider;
+
+/**
+ * Stackoverflow social provider. See https://api.stackexchange.com/docs/authentication
+ *
+ * @author Vlastimil Elias (velias at redhat dot com)
+ */
+public class StackoverflowIdentityProvider extends AbstractOAuth2IdentityProvider
+ implements SocialIdentityProvider {
+
+ private static final Logger log = Logger.getLogger(StackoverflowIdentityProvider.class);
+
+ public static final String AUTH_URL = "https://stackexchange.com/oauth";
+ public static final String TOKEN_URL = "https://stackexchange.com/oauth/access_token";
+ public static final String PROFILE_URL = "https://api.stackexchange.com/2.2/me?order=desc&sort=name&site=stackoverflow";
+ public static final String DEFAULT_SCOPE = "";
+
+ public StackoverflowIdentityProvider(StackOverflowIdentityProviderConfig config) {
+ super(config);
+ config.setAuthorizationUrl(AUTH_URL);
+ config.setTokenUrl(TOKEN_URL);
+ config.setUserInfoUrl(PROFILE_URL);
+ }
+
+ @Override
+ protected FederatedIdentity doGetFederatedIdentity(String accessToken) {
+ log.debug("doGetFederatedIdentity()");
+ try {
+
+ String URL = PROFILE_URL + "&access_token=" + accessToken + "&key=" + getConfig().getKey();
+ if (log.isDebugEnabled()) {
+ log.debug("StackOverflow profile request to: " + URL);
+ }
+ JsonNode profile = SimpleHttp.doGet(URL).asJson().get("items").get(0);
+
+ FederatedIdentity user = new FederatedIdentity(getJsonProperty(profile, "user_id"));
+
+ user.setUsername(extractUsernameFromProfileURL(getJsonProperty(profile, "link")));
+ user.setName(unescapeHtml3(getJsonProperty(profile, "display_name")));
+ // email is not provided
+ // user.setEmail(getJsonProperty(profile, "email"));
+
+ return user;
+ } catch (Exception e) {
+ throw new IdentityBrokerException("Could not obtain user profile from Stackoverflow: " + e.getMessage(), e);
+ }
+ }
+
+ protected static String extractUsernameFromProfileURL(String profileURL) {
+ if (isNotBlank(profileURL)) {
+
+ try {
+ log.debug("go to extract username from profile URL " + profileURL);
+ URL u = new URL(profileURL);
+ String path = u.getPath();
+ if (isNotBlank(path) && path.length() > 1) {
+ if (path.startsWith("/")) {
+ path = path.substring(1);
+ }
+ String[] pe = path.split("/");
+ if (pe.length >= 3) {
+ return URLDecoder.decode(pe[2], "UTF-8");
+ } else {
+ log.warn("Stackoverflow profile URL path is without third part: " + profileURL);
+ }
+ } else {
+ log.warn("Stackoverflow profile URL is without path part: " + profileURL);
+ }
+ } catch (MalformedURLException e) {
+ log.warn("Stackoverflow profile URL is malformed: " + profileURL);
+ } catch (Exception e) {
+ log.warn("Stackoverflow profile URL " + profileURL + " username extraction failed due: " + e.getMessage());
+ }
+ }
+ return null;
+ }
+
+ private static boolean isNotBlank(String s) {
+ return s != null && s.trim().length() > 0;
+ }
+
+ @Override
+ protected String getDefaultScopes() {
+ return DEFAULT_SCOPE;
+ }
+
+ public static final String unescapeHtml3(final String input) {
+ if (input == null)
+ return null;
+ StringWriter writer = null;
+ int len = input.length();
+ int i = 1;
+ int st = 0;
+ while (true) {
+ // look for '&'
+ while (i < len && input.charAt(i - 1) != '&')
+ i++;
+ if (i >= len)
+ break;
+
+ // found '&', look for ';'
+ int j = i;
+ while (j < len && j < i + MAX_ESCAPE + 1 && input.charAt(j) != ';')
+ j++;
+ if (j == len || j < i + MIN_ESCAPE || j == i + MAX_ESCAPE + 1) {
+ i++;
+ continue;
+ }
+
+ // found escape
+ if (input.charAt(i) == '#') {
+ // numeric escape
+ int k = i + 1;
+ int radix = 10;
+
+ final char firstChar = input.charAt(k);
+ if (firstChar == 'x' || firstChar == 'X') {
+ k++;
+ radix = 16;
+ }
+
+ try {
+ int entityValue = Integer.parseInt(input.substring(k, j), radix);
+
+ if (writer == null)
+ writer = new StringWriter(input.length());
+ writer.append(input.substring(st, i - 1));
+
+ if (entityValue > 0xFFFF) {
+ final char[] chrs = Character.toChars(entityValue);
+ writer.write(chrs[0]);
+ writer.write(chrs[1]);
+ } else {
+ writer.write(entityValue);
+ }
+
+ } catch (NumberFormatException ex) {
+ i++;
+ continue;
+ }
+ } else {
+ // named escape
+ CharSequence value = lookupMap.get(input.substring(i, j));
+ if (value == null) {
+ i++;
+ continue;
+ }
+
+ if (writer == null)
+ writer = new StringWriter(input.length());
+ writer.append(input.substring(st, i - 1));
+
+ writer.append(value);
+ }
+
+ // skip escape
+ st = j + 1;
+ i = st;
+ }
+
+ if (writer != null) {
+ writer.append(input.substring(st, len));
+ return writer.toString();
+ }
+ return input;
+ }
+
+ private static final String[][] ESCAPES = { { "\"", "quot" }, // " - double-quote
+ { "&", "amp" }, // & - ampersand
+ { "<", "lt" }, // < - less-than
+ { ">", "gt" }, // > - greater-than
+ };
+
+ private static final int MIN_ESCAPE = 2;
+ private static final int MAX_ESCAPE = 6;
+
+ private static final HashMap lookupMap;
+ static {
+ lookupMap = new HashMap();
+ for (final CharSequence[] seq : ESCAPES)
+ lookupMap.put(seq[1].toString(), seq[0]);
+ }
+}
diff --git a/social/stackoverflow/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProviderFactory.java b/social/stackoverflow/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProviderFactory.java
new file mode 100644
index 0000000000..d02c2d7261
--- /dev/null
+++ b/social/stackoverflow/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProviderFactory.java
@@ -0,0 +1,47 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Copyright 2015 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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.keycloak.social.stackoverflow;
+
+import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.social.SocialIdentityProviderFactory;
+
+/**
+ * @author Vlastimil Elias (velias at redhat dot com)
+ */
+public class StackoverflowIdentityProviderFactory extends
+ AbstractIdentityProviderFactory implements
+ SocialIdentityProviderFactory {
+
+ public static final String PROVIDER_ID = "stackoverflow";
+
+ @Override
+ public String getName() {
+ return "StackOverflow";
+ }
+
+ @Override
+ public StackoverflowIdentityProvider create(IdentityProviderModel model) {
+ return new StackoverflowIdentityProvider(new StackOverflowIdentityProviderConfig(model));
+ }
+
+ @Override
+ public String getId() {
+ return PROVIDER_ID;
+ }
+}
diff --git a/social/stackoverflow/src/main/resources/META-INF/services/org.keycloak.social.SocialIdentityProviderFactory b/social/stackoverflow/src/main/resources/META-INF/services/org.keycloak.social.SocialIdentityProviderFactory
new file mode 100644
index 0000000000..4bbbe9c0e6
--- /dev/null
+++ b/social/stackoverflow/src/main/resources/META-INF/services/org.keycloak.social.SocialIdentityProviderFactory
@@ -0,0 +1 @@
+org.keycloak.social.stackoverflow.StackoverflowIdentityProviderFactory
\ No newline at end of file
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderModelTest.java
index 84bbc9c51f..57886e7af2 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderModelTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderModelTest.java
@@ -25,6 +25,7 @@ import org.keycloak.social.github.GitHubIdentityProviderFactory;
import org.keycloak.social.google.GoogleIdentityProviderFactory;
import org.keycloak.social.twitter.TwitterIdentityProviderFactory;
import org.keycloak.social.linkedin.LinkedInIdentityProviderFactory;
+import org.keycloak.social.stackoverflow.StackoverflowIdentityProviderFactory;
import org.keycloak.testsuite.model.AbstractModelTest;
import java.util.Collections;
@@ -49,6 +50,7 @@ public abstract class AbstractIdentityProviderModelTest extends AbstractModelTes
this.expectedProviders.add(GitHubIdentityProviderFactory.PROVIDER_ID);
this.expectedProviders.add(TwitterIdentityProviderFactory.PROVIDER_ID);
this.expectedProviders.add(LinkedInIdentityProviderFactory.PROVIDER_ID);
+ this.expectedProviders.add(StackoverflowIdentityProviderFactory.PROVIDER_ID);
this.expectedProviders = Collections.unmodifiableSet(this.expectedProviders);
}
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 f596f1b272..eda8b6902c 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
@@ -17,6 +17,11 @@
*/
package org.keycloak.testsuite.broker;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
import org.junit.Test;
import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
import org.keycloak.broker.oidc.OIDCIdentityProvider;
@@ -36,15 +41,13 @@ import org.keycloak.social.github.GitHubIdentityProvider;
import org.keycloak.social.github.GitHubIdentityProviderFactory;
import org.keycloak.social.google.GoogleIdentityProvider;
import org.keycloak.social.google.GoogleIdentityProviderFactory;
-import org.keycloak.social.twitter.TwitterIdentityProvider;
-import org.keycloak.social.twitter.TwitterIdentityProviderFactory;
import org.keycloak.social.linkedin.LinkedInIdentityProvider;
import org.keycloak.social.linkedin.LinkedInIdentityProviderFactory;
-
-import java.io.IOException;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+import org.keycloak.social.stackoverflow.StackOverflowIdentityProviderConfig;
+import org.keycloak.social.stackoverflow.StackoverflowIdentityProvider;
+import org.keycloak.social.stackoverflow.StackoverflowIdentityProviderFactory;
+import org.keycloak.social.twitter.TwitterIdentityProvider;
+import org.keycloak.social.twitter.TwitterIdentityProviderFactory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -164,6 +167,8 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
assertTwitterIdentityProviderConfig(identityProvider);
} else if (LinkedInIdentityProviderFactory.PROVIDER_ID.equals(providerId)) {
assertLinkedInIdentityProviderConfig(identityProvider);
+ } else if (StackoverflowIdentityProviderFactory.PROVIDER_ID.equals(providerId)) {
+ assertStackoverflowIdentityProviderConfig(identityProvider);
} else {
continue;
}
@@ -262,8 +267,8 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
}
private void assertLinkedInIdentityProviderConfig(IdentityProviderModel identityProvider) {
- LinkedInIdentityProvider gitHubIdentityProvider = new LinkedInIdentityProviderFactory().create(identityProvider);
- OAuth2IdentityProviderConfig config = gitHubIdentityProvider.getConfig();
+ LinkedInIdentityProvider liIdentityProvider = new LinkedInIdentityProviderFactory().create(identityProvider);
+ OAuth2IdentityProviderConfig config = liIdentityProvider.getConfig();
assertEquals("model-linkedin", config.getAlias());
assertEquals(LinkedInIdentityProviderFactory.PROVIDER_ID, config.getProviderId());
@@ -278,6 +283,24 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
assertEquals(LinkedInIdentityProvider.PROFILE_URL, config.getUserInfoUrl());
}
+ private void assertStackoverflowIdentityProviderConfig(IdentityProviderModel identityProvider) {
+ StackoverflowIdentityProvider soIdentityProvider = new StackoverflowIdentityProviderFactory().create(identityProvider);
+ StackOverflowIdentityProviderConfig config = soIdentityProvider.getConfig();
+
+ assertEquals("model-stackoverflow", config.getAlias());
+ assertEquals(StackoverflowIdentityProviderFactory.PROVIDER_ID, config.getProviderId());
+ assertEquals(true, config.isEnabled());
+ assertEquals(true, config.isUpdateProfileFirstLogin());
+ assertEquals(false, config.isAuthenticateByDefault());
+ assertEquals(false, config.isStoreToken());
+ assertEquals("clientId", config.getClientId());
+ assertEquals("clientSecret", config.getClientSecret());
+ assertEquals("keyValue", config.getKey());
+ assertEquals(StackoverflowIdentityProvider.AUTH_URL, config.getAuthorizationUrl());
+ assertEquals(StackoverflowIdentityProvider.TOKEN_URL, config.getTokenUrl());
+ assertEquals(StackoverflowIdentityProvider.PROFILE_URL, config.getUserInfoUrl());
+ }
+
private void assertTwitterIdentityProviderConfig(IdentityProviderModel identityProvider) {
TwitterIdentityProvider twitterIdentityProvider = new TwitterIdentityProviderFactory().create(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 96cdc9602f..71744205a0 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
@@ -75,6 +75,21 @@
"clientSecret": "clientSecret"
}
},
+ {
+ "alias" : "model-stackoverflow",
+ "providerId" : "stackoverflow",
+ "enabled": true,
+ "updateProfileFirstLogin" : "true",
+ "storeToken": false,
+ "config": {
+ "key": "keyValue",
+ "authorizationUrl": "authorizationUrl",
+ "tokenUrl": "tokenUrl",
+ "userInfoUrl": "userInfoUrl",
+ "clientId": "clientId",
+ "clientSecret": "clientSecret"
+ }
+ },
{
"alias" : "model-saml-signed-idp",
"providerId" : "saml",