keycloak-uncached

Details

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 d4be887..c9050b8 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
@@ -1,5 +1,8 @@
 package org.keycloak.broker.oidc.util;
 
+import org.codehaus.jackson.JsonNode;
+import org.codehaus.jackson.map.ObjectMapper;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -12,140 +15,137 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.zip.GZIPInputStream;
 
-import org.codehaus.jackson.JsonNode;
-import org.codehaus.jackson.map.ObjectMapper;
-
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  * @author Vlastimil Elias (velias at redhat dot com)
  */
 public class SimpleHttp {
 
-	private static ObjectMapper mapper = new ObjectMapper();
-
-	private String url;
-	private String method;
-	private Map<String, String> headers;
-	private Map<String, String> params;
-
-	private SimpleHttp(String url, String method) {
-		this.url = url;
-		this.method = method;
-	}
-
-	public static SimpleHttp doGet(String url) {
-		return new SimpleHttp(url, "GET");
-	}
-
-	public static SimpleHttp doPost(String url) {
-		return new SimpleHttp(url, "POST");
-	}
-
-	public SimpleHttp header(String name, String value) {
-		if (headers == null) {
-			headers = new HashMap<String, String>();
-		}
-		headers.put(name, value);
-		return this;
-	}
-
-	public SimpleHttp param(String name, String value) {
-		if (params == null) {
-			params = new HashMap<String, String>();
-		}
-		params.put(name, value);
-		return this;
-	}
-
-	public JsonNode asJson() throws IOException {
-		return mapper.readTree(asString());
-	}
-
-	public String asString() throws IOException {
-		boolean get = method.equals("GET");
-		boolean post = method.equals("POST");
-
-		StringBuilder sb = new StringBuilder();
-		if (get) {
-			sb.append(url);
-		}
-
-		if (params != null) {
-			boolean f = true;
-			for (Map.Entry<String, String> p : params.entrySet()) {
-				if (f) {
-					f = false;
-					if (get) {
-						sb.append("?");
-					}
-				} else {
-					sb.append("&");
-				}
-				sb.append(URLEncoder.encode(p.getKey(), "UTF-8"));
-				sb.append("=");
-				sb.append(URLEncoder.encode(p.getValue(), "UTF-8"));
-			}
-		}
-
-		if (get) {
-			url = sb.toString();
-		}
-
-		HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
-		OutputStream os = null;
-		InputStream is = null;
-
-		try {
-			connection.setRequestMethod(method);
-
-			if (headers != null) {
-				for (Map.Entry<String, String> h : headers.entrySet()) {
-					connection.setRequestProperty(h.getKey(), h.getValue());
-				}
-			}
-
-			if (post) {
-				String data = sb.toString();
-
-				connection.setDoOutput(true);
-				connection.setRequestMethod("POST");
-				connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
-				connection.setRequestProperty("Content-Length", String.valueOf(data.length()));
-
-				os = connection.getOutputStream();
-				os.write(data.getBytes());
-			} else {
-				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) {
-				os.close();
-			}
-
-			if (is != null) {
-				is.close();
-			}
-		}
-	}
-
-	private String toString(InputStream is) throws IOException {
-		InputStreamReader reader = new InputStreamReader(is);
-
-		StringWriter writer = new StringWriter();
-
-		char[] buffer = new char[1024 * 4];
-		for (int n = reader.read(buffer); n != -1; n = reader.read(buffer)) {
-			writer.write(buffer, 0, n);
-		}
-
-		return writer.toString();
-	}
+    private static ObjectMapper mapper = new ObjectMapper();
+
+    private String url;
+    private String method;
+    private Map<String, String> headers;
+    private Map<String, String> params;
+
+    private SimpleHttp(String url, String method) {
+        this.url = url;
+        this.method = method;
+    }
+
+    public static SimpleHttp doGet(String url) {
+        return new SimpleHttp(url, "GET");
+    }
+
+    public static SimpleHttp doPost(String url) {
+        return new SimpleHttp(url, "POST");
+    }
+
+    public SimpleHttp header(String name, String value) {
+        if (headers == null) {
+            headers = new HashMap<String, String>();
+        }
+        headers.put(name, value);
+        return this;
+    }
+
+    public SimpleHttp param(String name, String value) {
+        if (params == null) {
+            params = new HashMap<String, String>();
+        }
+        params.put(name, value);
+        return this;
+    }
+
+    public JsonNode asJson() throws IOException {
+        return mapper.readTree(asString());
+    }
+
+    public String asString() throws IOException {
+        boolean get = method.equals("GET");
+        boolean post = method.equals("POST");
+
+        StringBuilder sb = new StringBuilder();
+        if (get) {
+            sb.append(url);
+        }
+
+        if (params != null) {
+            boolean f = true;
+            for (Map.Entry<String, String> p : params.entrySet()) {
+                if (f) {
+                    f = false;
+                    if (get) {
+                        sb.append("?");
+                    }
+                } else {
+                    sb.append("&");
+                }
+                sb.append(URLEncoder.encode(p.getKey(), "UTF-8"));
+                sb.append("=");
+                sb.append(URLEncoder.encode(p.getValue(), "UTF-8"));
+            }
+        }
+
+        if (get) {
+            url = sb.toString();
+        }
+
+        HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
+        OutputStream os = null;
+        InputStream is = null;
+
+        try {
+            connection.setRequestMethod(method);
+
+            if (headers != null) {
+                for (Map.Entry<String, String> h : headers.entrySet()) {
+                    connection.setRequestProperty(h.getKey(), h.getValue());
+                }
+            }
+
+            if (post) {
+                String data = sb.toString();
+
+                connection.setDoOutput(true);
+                connection.setRequestMethod("POST");
+                connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
+                connection.setRequestProperty("Content-Length", String.valueOf(data.length()));
+
+                os = connection.getOutputStream();
+                os.write(data.getBytes());
+            } else {
+                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) {
+                os.close();
+            }
+
+            if (is != null) {
+                is.close();
+            }
+        }
+    }
+
+    private String toString(InputStream is) throws IOException {
+        InputStreamReader reader = new InputStreamReader(is);
+
+        StringWriter writer = new StringWriter();
+
+        char[] buffer = new char[1024 * 4];
+        for (int n = reader.read(buffer); n != -1; n = reader.read(buffer)) {
+            writer.write(buffer, 0, n);
+        }
+
+        return writer.toString();
+    }
 
 }
diff --git a/distribution/modules/build.xml b/distribution/modules/build.xml
index ad0f9e7..80ff507 100755
--- a/distribution/modules/build.xml
+++ b/distribution/modules/build.xml
@@ -239,6 +239,10 @@
     	  <module-def name="org.keycloak.keycloak-social-linkedin">
     	      <maven-resource group="org.keycloak" artifact="keycloak-social-linkedin"/>
     	  </module-def>
+    	
+      	<module-def name="org.keycloak.keycloak-social-stackoverflow">
+    	      <maven-resource group="org.keycloak" artifact="keycloak-social-stackoverflow"/>
+    	  </module-def>
 
         <module-def name="org.keycloak.keycloak-kerberos-federation">
             <maven-resource group="org.keycloak" artifact="keycloak-kerberos-federation"/>
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 f8a251d..f553d24 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 @@
         <module name="org.keycloak.keycloak-social-google" services="import"/>
         <module name="org.keycloak.keycloak-social-twitter" services="import"/>
         <module name="org.keycloak.keycloak-social-linkedin" services="import"/>
+        <module name="org.keycloak.keycloak-social-stackoverflow" services="import"/>
         <module name="org.keycloak.keycloak-subsystem" services="import"/>
         <module name="org.keycloak.keycloak-timer-api" services="import"/>
         <module name="org.keycloak.keycloak-timer-basic" services="import"/>
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 6166b9b..86e86f4 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 @@
         <module name="org.keycloak.keycloak-social-google" services="import"/>
         <module name="org.keycloak.keycloak-social-twitter" services="import"/>
         <module name="org.keycloak.keycloak-social-linkedin" services="import"/>
+        <module name="org.keycloak.keycloak-social-stackoverflow" services="import"/>
         <module name="org.keycloak.keycloak-timer-api" services="import"/>
         <module name="org.keycloak.keycloak-timer-basic" services="import"/>
 
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 0000000..6ddd2a4
--- /dev/null
+++ b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-social-stackoverflow/main/module.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+
+
+<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-social-stackoverflow">
+    <resources>
+        <!-- Insert resources here -->
+    </resources>
+    <dependencies>
+        <module name="org.keycloak.keycloak-core"/>
+        <module name="org.keycloak.keycloak-social-core"/>
+        <module name="org.keycloak.keycloak-broker-core"/>
+        <module name="org.keycloak.keycloak-broker-oidc"/>
+        <module name="org.keycloak.keycloak-model-api"/>
+        <module name="org.jboss.logging"/>
+        <module name="javax.api"/>
+        <module name="org.codehaus.jackson.jackson-core-asl"/>
+        <module name="org.codehaus.jackson.jackson-mapper-asl"/>
+        <module name="org.codehaus.jackson.jackson-xc"/>
+    </dependencies>
+
+</module>
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 6a97ca5..6caa2c8 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 @@
             <module name="org.keycloak.keycloak-social-google" services="import"/>
             <module name="org.keycloak.keycloak-social-twitter" services="import"/>
             <module name="org.keycloak.keycloak-social-linkedin" services="import"/>
+            <module name="org.keycloak.keycloak-social-stackoverflow" services="import"/>
             <module name="org.keycloak.keycloak-timer-api" services="import"/>
             <module name="org.keycloak.keycloak-timer-basic" services="import"/>
             <module name="org.hibernate" services="import"/>
diff --git a/docbook/reference/en/en-US/modules/identity-broker.xml b/docbook/reference/en/en-US/modules/identity-broker.xml
index 3a74b73..5ebbbe2 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 @@
                     </tbody>
                 </tgroup>
             </table>
+        </section>
+        <section>
+            <title>StackOverflow</title>
+            <para>
+                To enable login with StackOverflow you first have to register an OAuth application on
+                <ulink url="https://stackapps.com/">StackApps</ulink>. Then you need to copy the client id, secret and key into the Keycloak Admin Console.
+            </para>
+            <para>
+                Let's see first how to create an application with StackOverflow.
+            </para>
+            <orderedlist>
+                <listitem>
+                    <para>
+                        Go to <ulink url="http://stackapps.com/apps/oauth/register">registering your application on Stack Apps</ulink> url and login here. 
+                        Use any value for <literal>Application Name</literal>, <literal>Application Website</literal> and <literal>Description</literal> you want.
+                        Set <literal>OAuth Domain</literal> to the domain where your Keycloak instance runs.
+                        Click the <literal>Register Your Application</literal> button.
+                    </para>
+                </listitem>
+                <listitem>
+                    <para>
+                        Copy <literal>Client Id</literal>, <literal>Client Secret</literal> and <literal>Key</literal> from the shown page.
+                    </para>
+                </listitem>
+            </orderedlist>
+            <para>
+                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:
+            </para>
+            <orderedlist>
+                <listitem>
+                    <para>
+                        Select the <literal>StackOverflow</literal> 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.
+                    </para>
+                </listitem>
+                <listitem>
+                    <para>
+                        Copy the client id, client secret and key to their corresponding fields in the Keycloak Admin Console. Click <literal>Save</literal>.
+                    </para>
+                </listitem>
+            </orderedlist>
+            <para>
+                That is it! This pretty much what you need to do in order to setup this identity provider.
+            </para>
+            <para>
+                The table below lists some additional configuration options you may use when configuring this provider.
+            </para>
+            <table>
+                <title>Configuration Options</title>
+                <tgroup align="left" cols="2">
+                    <thead>
+                        <row>
+                            <entry>
+                                Configuration
+                            </entry>
+                            <entry>
+                                Description
+                            </entry>
+                        </row>
+                    </thead>
+                    <tbody valign="top">
+                        <row>
+                            <entry>
+                                <literal>Default Scopes</literal>
+                            </entry>
+                            <entry>
+                                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 <ulink url="https://api.stackexchange.com/docs/authentication#scope">StackExchange API Authentication</ulink> documentation. Keycloak uses the empty scope by default.
+                            </entry>
+                        </row>
+                    </tbody>
+                </tgroup>
+            </table>
         </section>        
     </section>
 
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 345f594..2bc07bb 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 {

social/pom.xml 1(+1 -0)

diff --git a/social/pom.xml b/social/pom.xml
index ded7c60..ec08ca2 100755
--- a/social/pom.xml
+++ b/social/pom.xml
@@ -21,6 +21,7 @@
         <module>twitter</module>
         <module>facebook</module>
         <module>linkedin</module>
+        <module>stackoverflow</module>
     </modules>
 
 </project>
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
index b1cf142..40fb32f 100755
--- a/social/stackoverflow/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProvider.java
+++ b/social/stackoverflow/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProvider.java
@@ -32,7 +32,7 @@ import org.keycloak.broker.provider.IdentityBrokerException;
 import org.keycloak.social.SocialIdentityProvider;
 
 /**
- * Stackoverflow social provider. See https://developer.linkedin.com/docs/oauth2
+ * Stackoverflow social provider. See https://api.stackexchange.com/docs/authentication
  * 
  * @author Vlastimil Elias (velias at redhat dot com)
  */
@@ -67,14 +67,13 @@ public class StackoverflowIdentityProvider extends AbstractOAuth2IdentityProvide
 			FederatedIdentity user = new FederatedIdentity(getJsonProperty(profile, "user_id"));
 
 			user.setUsername(extractUsernameFromProfileURL(getJsonProperty(profile, "link")));
-			// TODO username contains html encoding of national chracters sometimes
 			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);
+			throw new IdentityBrokerException("Could not obtain user profile from Stackoverflow: " + e.getMessage(), e);
 		}
 	}
 
@@ -93,7 +92,7 @@ public class StackoverflowIdentityProvider extends AbstractOAuth2IdentityProvide
 					if (pe.length >= 3) {
 						return URLDecoder.decode(pe[2], "UTF-8");
 					} else {
-						log.warn("Stackoverflow profile URL path is without second part: " + profileURL);
+						log.warn("Stackoverflow profile URL path is without third part: " + profileURL);
 					}
 				} else {
 					log.warn("Stackoverflow profile URL is without path part: " + profileURL);
@@ -201,104 +200,6 @@ public class StackoverflowIdentityProvider extends AbstractOAuth2IdentityProvide
 			{ "&", "amp" }, // & - ampersand
 			{ "<", "lt" }, // < - less-than
 			{ ">", "gt" }, // > - greater-than
-
-			// Mapping to escape ISO-8859-1 characters to their named HTML 3.x equivalents.
-			{ "\u00A0", "nbsp" }, // non-breaking space
-			{ "\u00A1", "iexcl" }, // inverted exclamation mark
-			{ "\u00A2", "cent" }, // cent sign
-			{ "\u00A3", "pound" }, // pound sign
-			{ "\u00A4", "curren" }, // currency sign
-			{ "\u00A5", "yen" }, // yen sign = yuan sign
-			{ "\u00A6", "brvbar" }, // broken bar = broken vertical bar
-			{ "\u00A7", "sect" }, // section sign
-			{ "\u00A8", "uml" }, // diaeresis = spacing diaeresis
-			{ "\u00A9", "copy" }, // © - copyright sign
-			{ "\u00AA", "ordf" }, // feminine ordinal indicator
-			{ "\u00AB", "laquo" }, // left-pointing double angle quotation mark = left pointing guillemet
-			{ "\u00AC", "not" }, // not sign
-			{ "\u00AD", "shy" }, // soft hyphen = discretionary hyphen
-			{ "\u00AE", "reg" }, // ® - registered trademark sign
-			{ "\u00AF", "macr" }, // macron = spacing macron = overline = APL overbar
-			{ "\u00B0", "deg" }, // degree sign
-			{ "\u00B1", "plusmn" }, // plus-minus sign = plus-or-minus sign
-			{ "\u00B2", "sup2" }, // superscript two = superscript digit two = squared
-			{ "\u00B3", "sup3" }, // superscript three = superscript digit three = cubed
-			{ "\u00B4", "acute" }, // acute accent = spacing acute
-			{ "\u00B5", "micro" }, // micro sign
-			{ "\u00B6", "para" }, // pilcrow sign = paragraph sign
-			{ "\u00B7", "middot" }, // middle dot = Georgian comma = Greek middle dot
-			{ "\u00B8", "cedil" }, // cedilla = spacing cedilla
-			{ "\u00B9", "sup1" }, // superscript one = superscript digit one
-			{ "\u00BA", "ordm" }, // masculine ordinal indicator
-			{ "\u00BB", "raquo" }, // right-pointing double angle quotation mark = right pointing guillemet
-			{ "\u00BC", "frac14" }, // vulgar fraction one quarter = fraction one quarter
-			{ "\u00BD", "frac12" }, // vulgar fraction one half = fraction one half
-			{ "\u00BE", "frac34" }, // vulgar fraction three quarters = fraction three quarters
-			{ "\u00BF", "iquest" }, // inverted question mark = turned question mark
-			{ "\u00C0", "Agrave" }, // А - uppercase A, grave accent
-			{ "\u00C1", "Aacute" }, // Б - uppercase A, acute accent
-			{ "\u00C2", "Acirc" }, // В - uppercase A, circumflex accent
-			{ "\u00C3", "Atilde" }, // Г - uppercase A, tilde
-			{ "\u00C4", "Auml" }, // Д - uppercase A, umlaut
-			{ "\u00C5", "Aring" }, // Е - uppercase A, ring
-			{ "\u00C6", "AElig" }, // Ж - uppercase AE
-			{ "\u00C7", "Ccedil" }, // З - uppercase C, cedilla
-			{ "\u00C8", "Egrave" }, // И - uppercase E, grave accent
-			{ "\u00C9", "Eacute" }, // Й - uppercase E, acute accent
-			{ "\u00CA", "Ecirc" }, // К - uppercase E, circumflex accent
-			{ "\u00CB", "Euml" }, // Л - uppercase E, umlaut
-			{ "\u00CC", "Igrave" }, // М - uppercase I, grave accent
-			{ "\u00CD", "Iacute" }, // Н - uppercase I, acute accent
-			{ "\u00CE", "Icirc" }, // О - uppercase I, circumflex accent
-			{ "\u00CF", "Iuml" }, // П - uppercase I, umlaut
-			{ "\u00D0", "ETH" }, // Р - uppercase Eth, Icelandic
-			{ "\u00D1", "Ntilde" }, // С - uppercase N, tilde
-			{ "\u00D2", "Ograve" }, // Т - uppercase O, grave accent
-			{ "\u00D3", "Oacute" }, // У - uppercase O, acute accent
-			{ "\u00D4", "Ocirc" }, // Ф - uppercase O, circumflex accent
-			{ "\u00D5", "Otilde" }, // Х - uppercase O, tilde
-			{ "\u00D6", "Ouml" }, // Ц - uppercase O, umlaut
-			{ "\u00D7", "times" }, // multiplication sign
-			{ "\u00D8", "Oslash" }, // Ш - uppercase O, slash
-			{ "\u00D9", "Ugrave" }, // Щ - uppercase U, grave accent
-			{ "\u00DA", "Uacute" }, // Ъ - uppercase U, acute accent
-			{ "\u00DB", "Ucirc" }, // Ы - uppercase U, circumflex accent
-			{ "\u00DC", "Uuml" }, // Ь - uppercase U, umlaut
-			{ "\u00DD", "Yacute" }, // Э - uppercase Y, acute accent
-			{ "\u00DE", "THORN" }, // Ю - uppercase THORN, Icelandic
-			{ "\u00DF", "szlig" }, // Я - lowercase sharps, German
-			{ "\u00E0", "agrave" }, // а - lowercase a, grave accent
-			{ "\u00E1", "aacute" }, // б - lowercase a, acute accent
-			{ "\u00E2", "acirc" }, // в - lowercase a, circumflex accent
-			{ "\u00E3", "atilde" }, // г - lowercase a, tilde
-			{ "\u00E4", "auml" }, // д - lowercase a, umlaut
-			{ "\u00E5", "aring" }, // е - lowercase a, ring
-			{ "\u00E6", "aelig" }, // ж - lowercase ae
-			{ "\u00E7", "ccedil" }, // з - lowercase c, cedilla
-			{ "\u00E8", "egrave" }, // и - lowercase e, grave accent
-			{ "\u00E9", "eacute" }, // й - lowercase e, acute accent
-			{ "\u00EA", "ecirc" }, // к - lowercase e, circumflex accent
-			{ "\u00EB", "euml" }, // л - lowercase e, umlaut
-			{ "\u00EC", "igrave" }, // м - lowercase i, grave accent
-			{ "\u00ED", "iacute" }, // н - lowercase i, acute accent
-			{ "\u00EE", "icirc" }, // о - lowercase i, circumflex accent
-			{ "\u00EF", "iuml" }, // п - lowercase i, umlaut
-			{ "\u00F0", "eth" }, // р - lowercase eth, Icelandic
-			{ "\u00F1", "ntilde" }, // с - lowercase n, tilde
-			{ "\u00F2", "ograve" }, // т - lowercase o, grave accent
-			{ "\u00F3", "oacute" }, // у - lowercase o, acute accent
-			{ "\u00F4", "ocirc" }, // ф - lowercase o, circumflex accent
-			{ "\u00F5", "otilde" }, // х - lowercase o, tilde
-			{ "\u00F6", "ouml" }, // ц - lowercase o, umlaut
-			{ "\u00F7", "divide" }, // division sign
-			{ "\u00F8", "oslash" }, // ш - lowercase o, slash
-			{ "\u00F9", "ugrave" }, // щ - lowercase u, grave accent
-			{ "\u00FA", "uacute" }, // ъ - lowercase u, acute accent
-			{ "\u00FB", "ucirc" }, // ы - lowercase u, circumflex accent
-			{ "\u00FC", "uuml" }, // ь - lowercase u, umlaut
-			{ "\u00FD", "yacute" }, // э - lowercase y, acute accent
-			{ "\u00FE", "thorn" }, // ю - lowercase thorn, Icelandic
-			{ "\u00FF", "yuml" }, // я - lowercase y, umlaut
 	};
 
 	private static final int MIN_ESCAPE = 2;
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 84bbc9c..57886e7 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 f596f1b..eda8b69 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 96cdc96..7174420 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
@@ -76,6 +76,21 @@
             }
         },
         {
+            "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",
           "enabled": true,