Merge pull request #1083 from velias/KEYCLOAK-1046
KEYCLOAK-1046 - StackOverflow social login provider implementation
This commit is contained in:
commit
e9aac561d3
28 changed files with 516 additions and 11 deletions
|
@ -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 <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
* @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) {
|
||||
|
|
5
dependencies/server-all/pom.xml
vendored
5
dependencies/server-all/pom.xml
vendored
|
@ -127,6 +127,11 @@
|
|||
<artifactId>keycloak-social-linkedin</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-social-stackoverflow</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- ldap federation api -->
|
||||
<dependency>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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"/>
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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"/>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -28,12 +28,13 @@
|
|||
</div>
|
||||
<span tooltip-placement="right" tooltip="The client or application secret registered withing the identity provider." class="fa fa-info-circle"></span>
|
||||
</div>
|
||||
<div data-ng-include data-src="resourceUrl + '/partials/realm-identity-provider-' + identityProvider.providerId + '-ext.html'"></div>
|
||||
<div class="form-group clearfix">
|
||||
<label class="col-sm-2 control-label" for="defaultScope">Default Scopes </label>
|
||||
<div class="col-sm-4">
|
||||
<input class="form-control" id="defaultScope" type="text" ng-model="identityProvider.config.defaultScope">
|
||||
</div>
|
||||
<span tooltip-placement="right" tooltip="The scopes to be sent when asking for authorization. It can be a space-separated list of scopes. Defaults to 'openid'." class="fa fa-info-circle"></span>
|
||||
<span tooltip-placement="right" tooltip="The scopes to be sent when asking for authorization. See documentation for possible values, separator and default value'." class="fa fa-info-circle"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="enabled">Store Tokens</label>
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<div class="form-group clearfix">
|
||||
<label class="col-sm-2 control-label" for="clientId">Key <span class="required">*</span></label>
|
||||
<div class="col-sm-4">
|
||||
<input class="form-control" id="clientId" type="text" ng-model="identityProvider.config.key" required>
|
||||
</div>
|
||||
<span tooltip-placement="right" tooltip="The Key obtained from Stack Overflow application registration." class="fa fa-info-circle"></span>
|
||||
</div>
|
|
@ -0,0 +1 @@
|
|||
<div data-ng-include data-src="resourceUrl + '/partials/realm-identity-provider-social.html'"></div>
|
|
@ -238,7 +238,7 @@ ol#kc-totp-settings li:first-of-type {
|
|||
}
|
||||
|
||||
.zocial {
|
||||
width: 125px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.zocial:hover {
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
<module>twitter</module>
|
||||
<module>facebook</module>
|
||||
<module>linkedin</module>
|
||||
<module>stackoverflow</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
|
|
1
social/stackoverflow/.gitignore
vendored
Normal file
1
social/stackoverflow/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target/
|
40
social/stackoverflow/pom.xml
Executable file
40
social/stackoverflow/pom.xml
Executable file
|
@ -0,0 +1,40 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-social-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.2.0.Beta1-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<artifactId>keycloak-social-stackoverflow</artifactId>
|
||||
<name>Keycloak Social StackOverflow</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-social-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-broker-oidc</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-mapper-asl</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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<StackOverflowIdentityProviderConfig>
|
||||
implements SocialIdentityProvider<StackOverflowIdentityProviderConfig> {
|
||||
|
||||
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<String, CharSequence> lookupMap;
|
||||
static {
|
||||
lookupMap = new HashMap<String, CharSequence>();
|
||||
for (final CharSequence[] seq : ESCAPES)
|
||||
lookupMap.put(seq[1].toString(), seq[0]);
|
||||
}
|
||||
}
|
|
@ -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<StackoverflowIdentityProvider> implements
|
||||
SocialIdentityProviderFactory<StackoverflowIdentityProvider> {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
org.keycloak.social.stackoverflow.StackoverflowIdentityProviderFactory
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in a new issue