KEYCLOAK-1373 - added attribute importer for other social providers,

documented
This commit is contained in:
Vlastimil Elias 2015-06-09 13:49:58 +02:00
parent 74ace4006c
commit c6e0195a3c
16 changed files with 495 additions and 157 deletions

View file

@ -19,6 +19,7 @@ package org.keycloak.broker.oidc;
import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.JsonNode;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
import org.keycloak.broker.oidc.util.JsonSimpleHttp; import org.keycloak.broker.oidc.util.JsonSimpleHttp;
import org.keycloak.broker.provider.util.SimpleHttp; import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.broker.provider.AuthenticationRequest; import org.keycloak.broker.provider.AuthenticationRequest;
@ -50,6 +51,7 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import java.io.IOException; import java.io.IOException;
import java.security.PublicKey; import java.security.PublicKey;
@ -224,7 +226,7 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
name = getJsonProperty(userInfo, "name"); name = getJsonProperty(userInfo, "name");
preferredUsername = getJsonProperty(userInfo, "preferred_username"); preferredUsername = getJsonProperty(userInfo, "preferred_username");
email = getJsonProperty(userInfo, "email"); email = getJsonProperty(userInfo, "email");
identity.getContextData().put(USER_INFO, userInfo); AbstractJsonUserAttributeMapper.storeUserProfileForMapper(identity, userInfo, getConfig().getAlias());
} }
identity.getContextData().put(FEDERATED_ACCESS_TOKEN_RESPONSE, tokenResponse); identity.getContextData().put(FEDERATED_ACCESS_TOKEN_RESPONSE, tokenResponse);
identity.getContextData().put(VALIDATED_ID_TOKEN, idToken); identity.getContextData().put(VALIDATED_ID_TOKEN, idToken);

View file

@ -15,119 +15,192 @@ import org.keycloak.models.UserModel;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
/** /**
* Abstract class for Social Provider mappers which allow mapping of JSON user profile field into Keycloak user attribute. * Abstract class for Social Provider mappers which allow mapping of JSON user profile field into Keycloak user
* Concrete mapper classes with own ID and provider mapping must be implemented for each social provider who uses {@link JsonNode} user profile. * attribute. Concrete mapper classes with own ID and provider mapping must be implemented for each social provider who
* uses {@link JsonNode} user profile.
* *
* @author Vlastimil Elias (velias at redhat dot com) * @author Vlastimil Elias (velias at redhat dot com)
*/ */
public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityProviderMapper { public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityProviderMapper {
protected static final Logger logger = Logger.getLogger(AbstractJsonUserAttributeMapper.class);
protected static final Logger LOGGER_DUMP_USER_PROFILE = Logger.getLogger("org.keycloak.social.user_profile_dump"); protected static final Logger logger = Logger.getLogger(AbstractJsonUserAttributeMapper.class);
/** protected static final Logger LOGGER_DUMP_USER_PROFILE = Logger.getLogger("org.keycloak.social.user_profile_dump");
* Config param where name of mapping source JSON User Profile field is stored.
*/
public static final String CONF_JSON_FIELD = "jsonField";
/**
* Config param where name of mapping target USer attribute is stored.
*/
public static final String CONF_USER_ATTRIBUTE = "userAttribute";
/** private static final String JSON_PATH_DELIMITER = ".";
* Key in {@link BrokeredIdentityContext#getContextData()} where {@link JsonNode} with user profile is stored.
*/
public static final String CONTEXT_JSON_NODE = OIDCIdentityProvider.USER_INFO;
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>(); /**
* Config param where name of mapping source JSON User Profile field is stored.
*/
public static final String CONF_JSON_FIELD = "jsonField";
/**
* Config param where name of mapping target USer attribute is stored.
*/
public static final String CONF_USER_ATTRIBUTE = "userAttribute";
static { /**
ProviderConfigProperty property; * Key in {@link BrokeredIdentityContext#getContextData()} where {@link JsonNode} with user profile is stored.
ProviderConfigProperty property1; */
property1 = new ProviderConfigProperty(); public static final String CONTEXT_JSON_NODE = OIDCIdentityProvider.USER_INFO;
property1.setName(CONF_JSON_FIELD);
property1.setLabel("Social Profile JSON Field Name");
property1.setHelpText("Name of field in Social provider User Profile JSON data to get value from.");
property1.setType(ProviderConfigProperty.STRING_TYPE);
configProperties.add(property1);
property = new ProviderConfigProperty();
property.setName(CONF_USER_ATTRIBUTE);
property.setLabel("User Attribute Name");
property.setHelpText("User attribute name to store information into.");
property.setType(ProviderConfigProperty.STRING_TYPE);
configProperties.add(property);
}
public static void storeUserProfileForMapper(BrokeredIdentityContext user, JsonNode profile) { private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
user.getContextData().put(AbstractJsonUserAttributeMapper.CONTEXT_JSON_NODE, profile);
if (LOGGER_DUMP_USER_PROFILE.isDebugEnabled())
LOGGER_DUMP_USER_PROFILE.debug("User Profile JSON Data: " + profile);
}
@Override static {
public List<ProviderConfigProperty> getConfigProperties() { ProviderConfigProperty property;
return configProperties; ProviderConfigProperty property1;
} property1 = new ProviderConfigProperty();
property1.setName(CONF_JSON_FIELD);
property1.setLabel("Social Profile JSON Field Path");
property1.setHelpText("Path of field in Social provider User Profile JSON data to get value from. You can use dot notation for nesting and square brackets for array index. Eg. 'contact.address[0].country'.");
property1.setType(ProviderConfigProperty.STRING_TYPE);
configProperties.add(property1);
property = new ProviderConfigProperty();
property.setName(CONF_USER_ATTRIBUTE);
property.setLabel("User Attribute Name");
property.setHelpText("User attribute name to store information into.");
property.setType(ProviderConfigProperty.STRING_TYPE);
configProperties.add(property);
}
@Override /**
public String getDisplayCategory() { * Store used profile JsonNode into user context for later use by this mapper. Profile data are dumped into special logger if enabled also to allow investigation of the structure.
return "Attribute Importer"; *
} * @param user context to store profile data into
* @param profile to store into context
* @param provider identification of social provider to be used in log dump
*
* @see #importNewUser(KeycloakSession, RealmModel, UserModel, IdentityProviderMapperModel, BrokeredIdentityContext)
* @see BrokeredIdentityContext#getContextData()
*/
public static void storeUserProfileForMapper(BrokeredIdentityContext user, JsonNode profile, String provider) {
user.getContextData().put(AbstractJsonUserAttributeMapper.CONTEXT_JSON_NODE, profile);
if (LOGGER_DUMP_USER_PROFILE.isDebugEnabled())
LOGGER_DUMP_USER_PROFILE.debug("User Profile JSON Data for provider "+provider+": " + profile);
}
@Override @Override
public String getDisplayType() { public List<ProviderConfigProperty> getConfigProperties() {
return "Attribute Importer"; return configProperties;
} }
@Override @Override
public String getHelpText() { public String getDisplayCategory() {
return "Import user profile information if it exists in Social provider JSON data into the specified user attribute."; return "Attribute Importer";
} }
@Override @Override
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { public String getDisplayType() {
String attribute = mapperModel.getConfig().get(CONF_USER_ATTRIBUTE); return "Attribute Importer";
if (attribute == null) { }
logger.debug("Attribute is not configured");
return;
}
String value = getJsonValue(mapperModel, context); @Override
if (value != null) { public String getHelpText() {
user.setAttribute(attribute, value); return "Import user profile information if it exists in Social provider JSON data into the specified user attribute.";
} }
}
@Override @Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
// we do not update user profile from social provider String attribute = mapperModel.getConfig().get(CONF_USER_ATTRIBUTE);
} if (attribute == null || attribute.trim().isEmpty()) {
logger.debug("Attribute is not configured");
return;
}
attribute = attribute.trim();
protected static String getJsonValue(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { String value = getJsonValue(mapperModel, context);
if (value != null) {
user.setAttribute(attribute, value);
}
}
String jsonField = mapperModel.getConfig().get(CONF_JSON_FIELD); @Override
if (jsonField == null) { public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
logger.debug("JSON field is not configured"); // we do not update user profile from social provider
return null; }
}
JsonNode profileJsonNode = (JsonNode) context.getContextData().get(CONTEXT_JSON_NODE); protected static String getJsonValue(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
if (profileJsonNode != null) { String jsonField = mapperModel.getConfig().get(CONF_JSON_FIELD);
JsonNode value = profileJsonNode.get(jsonField); if (jsonField == null || jsonField.trim().isEmpty()) {
if (value != null) { logger.debug("JSON field path is not configured");
String ret = value.asText(); return null;
if (ret != null && !ret.trim().isEmpty()) }
return ret.trim(); jsonField = jsonField.trim();
else
return null;
}
} else {
logger.debug("User profile JSON node is not available.");
}
return null; if (jsonField.startsWith(JSON_PATH_DELIMITER) || jsonField.endsWith(JSON_PATH_DELIMITER) || jsonField.startsWith("[")) {
} logger.debug("JSON field path is invalid " + jsonField);
return null;
}
JsonNode profileJsonNode = (JsonNode) context.getContextData().get(CONTEXT_JSON_NODE);
String value = getJsonValue(profileJsonNode, jsonField);
if (value == null) {
logger.debug("User profile JSON value '" + jsonField + "' is not available.");
}
return value;
}
protected static String getJsonValue(JsonNode baseNode, String fieldPath) {
logger.debug("Going to process JsonNode path " + fieldPath + " on data " + baseNode);
if (baseNode != null) {
int idx = fieldPath.indexOf(JSON_PATH_DELIMITER);
String currentFieldName = fieldPath;
if (idx > 0) {
currentFieldName = fieldPath.substring(0, idx).trim();
if (currentFieldName.isEmpty()) {
logger.debug("JSON path is invalid " + fieldPath);
return null;
}
}
String currentNodeName = currentFieldName;
int arrayIndex = -1;
if (currentFieldName.endsWith("]")) {
int bi = currentFieldName.indexOf("[");
if (bi == -1) {
logger.debug("Invalid array index construct in " + currentFieldName);
return null;
}
try {
String is = currentFieldName.substring(bi+1, currentFieldName.length() - 1).trim();
arrayIndex = Integer.parseInt(is);
} catch (Exception e) {
logger.debug("Invalid array index construct in " + currentFieldName);
return null;
}
currentNodeName = currentFieldName.substring(0,bi).trim();
}
JsonNode currentNode = baseNode.get(currentNodeName);
if (arrayIndex > -1 && currentNode.isArray()) {
logger.debug("Going to take array node at index " + arrayIndex);
currentNode = currentNode.get(arrayIndex);
}
if (currentNode == null) {
logger.debug("JsonNode not found for name " + currentFieldName);
return null;
}
if (idx < 0) {
if (!currentNode.isValueNode()) {
logger.debug("JsonNode is not value node for name " + currentFieldName);
return null;
}
String ret = currentNode.asText();
if (ret != null && !ret.trim().isEmpty())
return ret.trim();
} else {
return getJsonValue(currentNode, fieldPath.substring(idx + 1));
}
}
return null;
}
} }

View file

@ -0,0 +1,120 @@
/*
* JBoss, Home of Professional Open Source
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @authors tag. All rights reserved.
*/
package org.keycloak.broker.oidc.mappers;
import java.io.IOException;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.junit.Assert;
import org.junit.Test;
/**
* Unit test for {@link AbstractJsonUserAttributeMapper}
*
* @author Vlastimil Elias (velias at redhat dot com)
*/
public class AbstractJsonUserAttributeMapperTest {
private static ObjectMapper mapper = new ObjectMapper();
private static JsonNode baseNode;
private JsonNode getJsonNode() throws JsonProcessingException, IOException {
if (baseNode == null)
baseNode = mapper.readTree("{ \"value1\" : \"v1 \",\"value_empty\" : \"\", \"value_b\" : true, \"value_i\" : 454, " + " \"value_array\":[\"a1\",\"a2\"], " +" \"nest1\": {\"value1\": \" fgh \",\"value_empty\" : \"\", \"nest2\":{\"value_b\" : false, \"value_i\" : 43}}, "+ " \"nesta\": { \"a\":[{\"av1\": \"vala1\"},{\"av1\": \"vala2\"}]}"+" }");
return baseNode;
}
@Test
public void getJsonValue_invalidPath() throws JsonProcessingException, IOException {
Assert.assertNull(AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "."));
Assert.assertNull(AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), ".."));
Assert.assertNull(AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "...value1"));
Assert.assertNull(AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), ".value1"));
Assert.assertNull(AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value1."));
Assert.assertNull(AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "[]"));
Assert.assertNull(AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "[value1"));
}
@Test
public void getJsonValue_simpleValues() throws JsonProcessingException, IOException {
//unknown field returns null
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_unknown"));
// we check value is trimmed also!
Assert.assertEquals("v1", AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value1"));
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_empty"));
Assert.assertEquals("true", AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_b"));
Assert.assertEquals("454", AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_i"));
}
@Test
public void getJsonValue_nestedSimpleValues() throws JsonProcessingException, IOException {
// null if path points to JSON object
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nest1"));
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nest1.nest2"));
//unknown field returns null
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nest1.value_unknown"));
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nest1.nest2.value_unknown"));
// we check value is trimmed also!
Assert.assertEquals("fgh", AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nest1.value1"));
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nest1.value_empty"));
Assert.assertEquals("false", AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nest1.nest2.value_b"));
Assert.assertEquals("43", AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nest1.nest2.value_i"));
// null if invalid nested path
Assert.assertNull(AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nest1."));
Assert.assertNull(AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nest1.nest2."));
}
@Test
public void getJsonValue_simpleArray() throws JsonProcessingException, IOException {
// array field itself returns null if no index is provided
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_array"));
// outside index returns null
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_array[2]"));
//corect index
Assert.assertEquals("a1", AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_array[0]"));
Assert.assertEquals("a2", AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_array[1]"));
//incorrect array constructs
Assert.assertNull(AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_array[]"));
Assert.assertNull(AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_array]"));
Assert.assertNull(AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_array["));
Assert.assertNull(AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_array[a]"));
Assert.assertNull(AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_array[-2]"));
}
@Test
public void getJsonValue_nestedArrayWithObjects() throws JsonProcessingException, IOException {
Assert.assertEquals("vala1", AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nesta.a[0].av1"));
Assert.assertEquals("vala2", AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nesta.a[1].av1"));
//different path erros or nonexisting indexes or fields return null
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nesta.a[2].av1"));
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nesta.a[0]"));
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nesta.a[0].av_unknown"));
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nesta.a[].av1"));
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nesta.a"));
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nesta.a.av1"));
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nesta.a].av1"));
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nesta.a[.av1"));
}
}

View file

@ -1246,6 +1246,24 @@ keycloak.createLoginUrl({
the tool tips to see what each mapper can do for you. the tool tips to see what each mapper can do for you.
</para> </para>
</section> </section>
<section>
<title>Mapping/Importing User profile data from Social Identity Provider</title>
<para>
You can import user profile data provided by social identity providers like Google, GitHub, LinkedIn, Stackoverflow and Facebook
into new Keycloak user created from given social accounts. After you configure a broker, you'll see a <literal>Mappers</literal>
button appear. Click on that and you'll get to the list of mappers that are assigned to this broker. There is a
<literal>Create</literal> button on this page. Clicking on this create button allows you to create a broker mapper.
"Attribute Importer" mapper allows you to define path in JSON user profile data provided by the provider to get value from.
You can use dot notation for nesting and square brackets to access fields in array by index. For example 'contact.address[0].country'.
Then you can define name of Keycloak's user profile attribute this value is stored into.
</para>
<para>
To investigate structure of user profile JSON data provided by social providers you can enable <literal>DEBUG</literal> level for
logger <literal>org.keycloak.social.user_profile_dump</literal> and login using given provider. Then you can find user profile
JSON structure in Keycloak log file.
</para>
</section>
<section> <section>
<title>Examples</title> <title>Examples</title>

View file

@ -3,10 +3,11 @@ package org.keycloak.social.facebook;
import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.JsonNode;
import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider; import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig; import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
import org.keycloak.broker.oidc.util.JsonSimpleHttp; import org.keycloak.broker.oidc.util.JsonSimpleHttp;
import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.IdentityBrokerException; import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.social.SocialIdentityProvider; import org.keycloak.social.SocialIdentityProvider;
/** /**
@ -14,63 +15,65 @@ import org.keycloak.social.SocialIdentityProvider;
*/ */
public class FacebookIdentityProvider extends AbstractOAuth2IdentityProvider implements SocialIdentityProvider { public class FacebookIdentityProvider extends AbstractOAuth2IdentityProvider implements SocialIdentityProvider {
public static final String AUTH_URL = "https://graph.facebook.com/oauth/authorize"; public static final String AUTH_URL = "https://graph.facebook.com/oauth/authorize";
public static final String TOKEN_URL = "https://graph.facebook.com/oauth/access_token"; public static final String TOKEN_URL = "https://graph.facebook.com/oauth/access_token";
public static final String PROFILE_URL = "https://graph.facebook.com/me"; public static final String PROFILE_URL = "https://graph.facebook.com/me";
public static final String DEFAULT_SCOPE = "email"; public static final String DEFAULT_SCOPE = "email";
public FacebookIdentityProvider(OAuth2IdentityProviderConfig config) { public FacebookIdentityProvider(OAuth2IdentityProviderConfig config) {
super(config); super(config);
config.setAuthorizationUrl(AUTH_URL); config.setAuthorizationUrl(AUTH_URL);
config.setTokenUrl(TOKEN_URL); config.setTokenUrl(TOKEN_URL);
config.setUserInfoUrl(PROFILE_URL); config.setUserInfoUrl(PROFILE_URL);
} }
protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) { protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) {
try { try {
JsonNode profile = JsonSimpleHttp.asJson(SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken)); JsonNode profile = JsonSimpleHttp.asJson(SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken));
String id = getJsonProperty(profile, "id"); String id = getJsonProperty(profile, "id");
BrokeredIdentityContext user = new BrokeredIdentityContext(id); BrokeredIdentityContext user = new BrokeredIdentityContext(id);
String email = getJsonProperty(profile, "email"); String email = getJsonProperty(profile, "email");
user.setEmail(email); user.setEmail(email);
String username = getJsonProperty(profile, "username"); String username = getJsonProperty(profile, "username");
if (username == null) { if (username == null) {
if (email != null) { if (email != null) {
username = email; username = email;
} else { } else {
username = id; username = id;
} }
} }
user.setUsername(username); user.setUsername(username);
String firstName = getJsonProperty(profile, "first_name"); String firstName = getJsonProperty(profile, "first_name");
String lastName = getJsonProperty(profile, "last_name"); String lastName = getJsonProperty(profile, "last_name");
if (lastName == null) { if (lastName == null) {
lastName = ""; lastName = "";
} else { } else {
lastName = " " + lastName; lastName = " " + lastName;
} }
user.setName(firstName + lastName); user.setName(firstName + lastName);
user.setIdpConfig(getConfig()); user.setIdpConfig(getConfig());
user.setIdp(this); user.setIdp(this);
return user; AbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile, getConfig().getAlias());
} catch (Exception e) {
throw new IdentityBrokerException("Could not obtain user profile from facebook.", e);
}
}
@Override return user;
protected String getDefaultScopes() { } catch (Exception e) {
return DEFAULT_SCOPE; throw new IdentityBrokerException("Could not obtain user profile from facebook.", e);
} }
}
@Override
protected String getDefaultScopes() {
return DEFAULT_SCOPE;
}
} }

View file

@ -0,0 +1,29 @@
/*
* JBoss, Home of Professional Open Source
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @authors tag. All rights reserved.
*/
package org.keycloak.social.facebook;
import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
/**
* User attribute mapper.
*
* @author Vlastimil Elias (velias at redhat dot com)
*/
public class FacebookUserAttributeMapper extends AbstractJsonUserAttributeMapper {
private static final String[] cp = new String[] { FacebookIdentityProviderFactory.PROVIDER_ID };
@Override
public String[] getCompatibleProviders() {
return cp;
}
@Override
public String getId() {
return "facebook-user-attribute-mapper";
}
}

View file

@ -0,0 +1 @@
org.keycloak.social.facebook.FacebookUserAttributeMapper

View file

@ -41,7 +41,7 @@ public class GitHubIdentityProvider extends AbstractOAuth2IdentityProvider imple
user.setIdpConfig(getConfig()); user.setIdpConfig(getConfig());
user.setIdp(this); user.setIdp(this);
AbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile); AbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile, getConfig().getAlias());
return user; return user;
} catch (Exception e) { } catch (Exception e) {

View file

@ -0,0 +1,29 @@
/*
* JBoss, Home of Professional Open Source
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @authors tag. All rights reserved.
*/
package org.keycloak.social.google;
import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
/**
* User attribute mapper.
*
* @author Vlastimil Elias (velias at redhat dot com)
*/
public class GoogleUserAttributeMapper extends AbstractJsonUserAttributeMapper {
private static final String[] cp = new String[] { GoogleIdentityProviderFactory.PROVIDER_ID };
@Override
public String[] getCompatibleProviders() {
return cp;
}
@Override
public String getId() {
return "google-user-attribute-mapper";
}
}

View file

@ -0,0 +1 @@
org.keycloak.social.google.GoogleUserAttributeMapper

View file

@ -25,10 +25,11 @@ import org.codehaus.jackson.JsonNode;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider; import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig; import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
import org.keycloak.broker.oidc.util.JsonSimpleHttp; import org.keycloak.broker.oidc.util.JsonSimpleHttp;
import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.IdentityBrokerException; import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.social.SocialIdentityProvider; import org.keycloak.social.SocialIdentityProvider;
/** /**
@ -58,16 +59,18 @@ public class LinkedInIdentityProvider extends AbstractOAuth2IdentityProvider imp
try { try {
JsonNode profile = JsonSimpleHttp.asJson(SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken)); JsonNode profile = JsonSimpleHttp.asJson(SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken));
BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "id")); BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "id"));
String username = extractUsernameFromProfileURL(getJsonProperty(profile, "publicProfileUrl")); String username = extractUsernameFromProfileURL(getJsonProperty(profile, "publicProfileUrl"));
user.setUsername(username); user.setUsername(username);
user.setName(getJsonProperty(profile, "formattedName")); user.setName(getJsonProperty(profile, "formattedName"));
user.setEmail(getJsonProperty(profile, "emailAddress")); user.setEmail(getJsonProperty(profile, "emailAddress"));
user.setIdpConfig(getConfig()); user.setIdpConfig(getConfig());
user.setIdp(this); user.setIdp(this);
return user; AbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile, getConfig().getAlias());
return user;
} catch (Exception e) { } catch (Exception e) {
throw new IdentityBrokerException("Could not obtain user profile from linkedIn.", e); throw new IdentityBrokerException("Could not obtain user profile from linkedIn.", e);
} }

View file

@ -0,0 +1,29 @@
/*
* JBoss, Home of Professional Open Source
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @authors tag. All rights reserved.
*/
package org.keycloak.social.linkedin;
import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
/**
* User attribute mapper.
*
* @author Vlastimil Elias (velias at redhat dot com)
*/
public class LinkedInUserAttributeMapper extends AbstractJsonUserAttributeMapper {
private static final String[] cp = new String[] { LinkedInIdentityProviderFactory.PROVIDER_ID };
@Override
public String[] getCompatibleProviders() {
return cp;
}
@Override
public String getId() {
return "linkedin-user-attribute-mapper";
}
}

View file

@ -0,0 +1 @@
org.keycloak.social.linkedin.LinkedInUserAttributeMapper

View file

@ -26,10 +26,11 @@ import java.util.HashMap;
import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.JsonNode;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider; import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
import org.keycloak.broker.oidc.util.JsonSimpleHttp; import org.keycloak.broker.oidc.util.JsonSimpleHttp;
import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.IdentityBrokerException; import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.social.SocialIdentityProvider; import org.keycloak.social.SocialIdentityProvider;
/** /**
@ -37,8 +38,7 @@ import org.keycloak.social.SocialIdentityProvider;
* *
* @author Vlastimil Elias (velias at redhat dot com) * @author Vlastimil Elias (velias at redhat dot com)
*/ */
public class StackoverflowIdentityProvider extends AbstractOAuth2IdentityProvider<StackOverflowIdentityProviderConfig> public class StackoverflowIdentityProvider extends AbstractOAuth2IdentityProvider<StackOverflowIdentityProviderConfig> implements SocialIdentityProvider<StackOverflowIdentityProviderConfig> {
implements SocialIdentityProvider<StackOverflowIdentityProviderConfig> {
private static final Logger log = Logger.getLogger(StackoverflowIdentityProvider.class); private static final Logger log = Logger.getLogger(StackoverflowIdentityProvider.class);
@ -54,8 +54,6 @@ public class StackoverflowIdentityProvider extends AbstractOAuth2IdentityProvide
config.setUserInfoUrl(PROFILE_URL); config.setUserInfoUrl(PROFILE_URL);
} }
@Override @Override
protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) { protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) {
log.debug("doGetFederatedIdentity()"); log.debug("doGetFederatedIdentity()");
@ -67,18 +65,19 @@ public class StackoverflowIdentityProvider extends AbstractOAuth2IdentityProvide
} }
JsonNode profile = JsonSimpleHttp.asJson(SimpleHttp.doGet(URL)).get("items").get(0); JsonNode profile = JsonSimpleHttp.asJson(SimpleHttp.doGet(URL)).get("items").get(0);
BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "user_id")); BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "user_id"));
String username = extractUsernameFromProfileURL(getJsonProperty(profile, "link")); String username = extractUsernameFromProfileURL(getJsonProperty(profile, "link"));
user.setUsername(username); user.setUsername(username);
user.setName(unescapeHtml3(getJsonProperty(profile, "display_name"))); user.setName(unescapeHtml3(getJsonProperty(profile, "display_name")));
// email is not provided // email is not provided
// user.setEmail(getJsonProperty(profile, "email")); // user.setEmail(getJsonProperty(profile, "email"));
user.setIdpConfig(getConfig()); user.setIdpConfig(getConfig());
user.setIdp(this); user.setIdp(this);
AbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile, getConfig().getAlias());
return user; return user;
} catch (Exception e) { } catch (Exception e) {
throw new IdentityBrokerException("Could not obtain user profile from Stackoverflow: " + e.getMessage(), e); throw new IdentityBrokerException("Could not obtain user profile from Stackoverflow: " + e.getMessage(), e);
} }

View file

@ -0,0 +1,29 @@
/*
* JBoss, Home of Professional Open Source
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @authors tag. All rights reserved.
*/
package org.keycloak.social.stackoverflow;
import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
/**
* User attribute mapper.
*
* @author Vlastimil Elias (velias at redhat dot com)
*/
public class StackoverflowUserAttributeMapper extends AbstractJsonUserAttributeMapper {
private static final String[] cp = new String[] { StackoverflowIdentityProviderFactory.PROVIDER_ID };
@Override
public String[] getCompatibleProviders() {
return cp;
}
@Override
public String getId() {
return "stackoverflow-user-attribute-mapper";
}
}

View file

@ -0,0 +1 @@
org.keycloak.social.stackoverflow.StackoverflowUserAttributeMapper