Merge upstream-master into tkyjovsk-master

This commit is contained in:
Tomas Kyjovsky 2015-06-10 23:27:36 +02:00
commit 30359b51fa
81 changed files with 695 additions and 249 deletions

View file

@ -19,6 +19,7 @@ package org.keycloak.broker.oidc;
import org.codehaus.jackson.JsonNode;
import org.jboss.logging.Logger;
import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
import org.keycloak.broker.oidc.util.JsonSimpleHttp;
import org.keycloak.broker.provider.util.SimpleHttp;
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.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
import java.security.PublicKey;
@ -224,7 +226,7 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
name = getJsonProperty(userInfo, "name");
preferredUsername = getJsonProperty(userInfo, "preferred_username");
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(VALIDATED_ID_TOKEN, idToken);

View file

@ -0,0 +1,206 @@
package org.keycloak.broker.oidc.mappers;
import java.util.ArrayList;
import java.util.List;
import org.codehaus.jackson.JsonNode;
import org.jboss.logging.Logger;
import org.keycloak.broker.oidc.OIDCIdentityProvider;
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.provider.ProviderConfigProperty;
/**
* Abstract class for Social Provider mappers which allow mapping of JSON user profile field into Keycloak user
* 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)
*/
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");
private static final String JSON_PATH_DELIMITER = ".";
/**
* 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";
/**
* 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>();
static {
ProviderConfigProperty property;
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);
}
/**
* 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.
*
* @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
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public String getDisplayCategory() {
return "Attribute Importer";
}
@Override
public String getDisplayType() {
return "Attribute Importer";
}
@Override
public String getHelpText() {
return "Import user profile information if it exists in Social provider JSON data into the specified user attribute.";
}
@Override
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String attribute = mapperModel.getConfig().get(CONF_USER_ATTRIBUTE);
if (attribute == null || attribute.trim().isEmpty()) {
logger.debug("Attribute is not configured");
return;
}
attribute = attribute.trim();
String value = getJsonValue(mapperModel, context);
if (value != null) {
user.setAttribute(attribute, value);
}
}
@Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
// we do not update user profile from social provider
}
protected static String getJsonValue(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String jsonField = mapperModel.getConfig().get(CONF_JSON_FIELD);
if (jsonField == null || jsonField.trim().isEmpty()) {
logger.debug("JSON field path is not configured");
return null;
}
jsonField = jsonField.trim();
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.
</para>
</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>
<title>Examples</title>

View file

@ -1,5 +1,5 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<h1><strong>Settings</strong> {{realm.realm|capitalize}}</h1>
<h1>Settings</h1>
<kc-tabs-realm></kc-tabs-realm>

View file

@ -5,7 +5,7 @@
<li>{{client.clientId}}</li>
</ol>
<h1><strong>Client</strong> {{client.clientId|capitalize}}</h1>
<h1>{{client.clientId|capitalize}}</h1>
<kc-tabs-client></kc-tabs-client>

View file

@ -5,7 +5,7 @@
<li>{{client.clientId}}</li>
</ol>
<h1><strong>Client</strong> {{client.clientId|capitalize}}</h1>
<h1>{{client.clientId|capitalize}}</h1>
<kc-tabs-client></kc-tabs-client>

View file

@ -6,8 +6,8 @@
<li data-ng-hide="create">{{client.clientId}}</li>
</ol>
<h1 data-ng-show="create"><strong>Add Client</strong></h1>
<h1 data-ng-hide="create"><strong>Client</strong> {{client.clientId|capitalize}}</h1>
<h1 data-ng-show="create">Add Client</h1>
<h1 data-ng-hide="create">{{client.clientId|capitalize}}</h1>
<kc-tabs-client></kc-tabs-client>

View file

@ -5,7 +5,7 @@
<li>Import Client</li>
</ol>
<h1><strong>Import Client</strong></h1>
<h1>Import Client</h1>
<form class="form-horizontal" name="realmForm" novalidate>
<fieldset class="border-top">

View file

@ -5,7 +5,7 @@
<li>{{client.clientId}}</li>
</ol>
<h1><strong>Client</strong> {{client.clientId|capitalize}}</h1>
<h1>{{client.clientId|capitalize}}</h1>
<kc-tabs-client></kc-tabs-client>

View file

@ -5,7 +5,7 @@
<li>{{client.clientId}}</li>
</ol>
<h1><strong>Client</strong> {{client.clientId|capitalize}}</h1>
<h1>{{client.clientId|capitalize}}</h1>
<kc-tabs-client></kc-tabs-client>

View file

@ -1,6 +1,6 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<h1>
<span><strong>Clients</strong> {{realm.realm|capitalize}}</span>
<span>Clients</span>
<kc-tooltip>Clients are trusted browser apps and web services in a realm. These clients can request a login. You can also define client specific roles.</kc-tooltip>
</h1>

View file

@ -7,7 +7,7 @@
<li class="active">Add Builtin Protocol Mappers</li>
</ol>
<h1><strong>Add Builtin Protocol Mapper</strong></h1>
<h1>Add Builtin Protocol Mapper</h1>
<table class="table table-striped table-bordered">
<thead>

View file

@ -5,7 +5,7 @@
<li>{{client.clientId}}</li>
</ol>
<h1><strong>Client</strong> {{client.clientId|capitalize}}</h1>
<h1>{{client.clientId|capitalize}}</h1>
<kc-tabs-client></kc-tabs-client>

View file

@ -5,7 +5,7 @@
<li>{{client.clientId}}</li>
</ol>
<h1><strong>Client</strong> {{client.clientId|capitalize}}</h1>
<h1>{{client.clientId|capitalize}}</h1>
<kc-tabs-client></kc-tabs-client>

View file

@ -8,8 +8,8 @@
<li data-ng-hide="create">{{role.name}}</li>
</ol>
<h1 data-ng-show="create"><strong>Add Role</strong></h1>
<h1 data-ng-hide="create"><strong>Role</strong> {{role.name}}</h1>
<h1 data-ng-show="create">Add Role</h1>
<h1 data-ng-hide="create">{{role.name|capitalize}}</h1>
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageClients">

View file

@ -5,7 +5,7 @@
<li>{{client.clientId}}</li>
</ol>
<h1><strong>Client</strong> {{client.clientId|capitalize}}</h1>
<h1>{{client.clientId|capitalize}}</h1>
<kc-tabs-client></kc-tabs-client>

View file

@ -7,7 +7,7 @@
<li class="active">SAML {{keyType}} Key Export</li>
</ol>
<h1><strong>Export SAML Key</strong> {{client.clientId|capitalize}}</h1>
<h1>Export SAML Key {{client.clientId|capitalize}}</h1>
<form class="form-horizontal" name="keyForm" novalidate kc-read-only="!access.manageRealm">
<fieldset class="form-group col-sm-10">

View file

@ -7,7 +7,7 @@
<li class="active">SAML {{keyType}} Key Import</li>
</ol>
<h1><strong>Import SAML Key</strong> {{client.clientId|capitalize}}</h1>
<h1>Import SAML Key {{client.clientId|capitalize}}</h1>
<form class="form-horizontal" name="keyForm" novalidate kc-read-only="!access.manageRealm">
<fieldset>

View file

@ -5,7 +5,7 @@
<li>{{client.clientId}}</li>
</ol>
<h1><strong>Client</strong> {{client.clientId|capitalize}}</h1>
<h1>{{client.clientId|capitalize}}</h1>
<kc-tabs-client></kc-tabs-client>

View file

@ -5,7 +5,7 @@
<li>{{client.clientId}}</li>
</ol>
<h1><strong>Client</strong> {{client.clientId|capitalize}}</h1>
<h1>{{client.clientId|capitalize}}</h1>
<kc-tabs-client></kc-tabs-client>

View file

@ -5,7 +5,7 @@
<li>{{client.clientId}}</li>
</ol>
<h1><strong>Client</strong> {{client.clientId|capitalize}}</h1>
<h1>{{client.clientId|capitalize}}</h1>
<kc-tabs-client></kc-tabs-client>

View file

@ -1,5 +1,5 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<h1><strong>Settings</strong> {{realm.realm|capitalize}}</h1>
<h1>Settings</h1>
<kc-tabs-realm></kc-tabs-realm>

View file

@ -5,8 +5,8 @@
<li data-ng-show="create">Add User Federation Provider</li>
</ol>
<h1 data-ng-hide="create"><strong>{{instance.providerName|capitalize}} User Federation Provider</strong> {{instance.displayName|capitalize}}</h1>
<h1 data-ng-show="create"><strong>Add {{instance.providerName|capitalize}} User Federation Provider</strong></h1>
<h1 data-ng-hide="create">{{instance.providerName|capitalize}}</h1>
<h1 data-ng-show="create">Add {{instance.providerName|capitalize}} User Federation Provide</h1>
<ul class="nav nav-tabs" data-ng-hide="create">
<li class="active"><a href="#/realms/{{realm.realm}}/user-federation/providers/{{instance.providerName}}/{{instance.id}}">Settings</a></li>

View file

@ -5,8 +5,8 @@
<li data-ng-show="create">Add User Federation Provider</li>
</ol>
<h1 data-ng-hide="create"><strong>Kerberos User Federation Provider</strong> {{instance.displayName|capitalize}}</h1>
<h1 data-ng-show="create"><strong>Add Kerberos User Federation Provider</strong></h1>
<h1 data-ng-hide="create">Kerberos</h1>
<h1 data-ng-show="create">Add Kerberos User Federation Provider</h1>
<ul class="nav nav-tabs" data-ng-hide="create">
<li class="active"><a href="#/realms/{{realm.realm}}/user-federation/providers/{{instance.providerName}}/{{instance.id}}">Settings</a></li>

View file

@ -5,8 +5,8 @@
<li data-ng-show="create">Add User Federation Provider</li>
</ol>
<h1 data-ng-hide="create"><strong>LDAP User Federation Provider</strong> {{instance.displayName|capitalize}}</h1>
<h1 data-ng-show="create"><strong>Add LDAP User Federation Provider</strong></h1>
<h1 data-ng-hide="create">LDAP</h1>
<h1 data-ng-show="create">Add LDAP User Federation Provider</h1>
<ul class="nav nav-tabs" data-ng-hide="create">
<li class="active"><a href="#/realms/{{realm.realm}}/user-federation/providers/{{instance.providerName}}/{{instance.id}}">Settings</a></li>

View file

@ -7,8 +7,8 @@
<li class="active" data-ng-hide="create">{{mapper.name}}</li>
</ol>
<h1 data-ng-hide="create"><strong>User Federation Mapper</strong> {{mapper.name}}</h1>
<h1 data-ng-show="create"><strong>Add User Federation Mapper</strong></h1>
<h1 data-ng-hide="create">{{mapper.name|capitalize}}</h1>
<h1 data-ng-show="create">Add User Federation Mapper</h1>
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
<fieldset>

View file

@ -5,7 +5,7 @@
<li>User Federation Mappers</li>
</ol>
<h1><strong>{{provider.providerName === 'ldap' ? 'LDAP' : (provider.providerName|capitalize)}} User Federation Provider</strong> {{provider.displayName|capitalize}}</h1>
<h1>{{provider.providerName === 'ldap' ? 'LDAP' : (provider.providerName|capitalize)}}</h1>
<ul class="nav nav-tabs" data-ng-hide="create">
<li><a href="#/realms/{{realm.realm}}/user-federation/providers/{{provider.providerName}}/{{provider.id}}">Settings</a></li>

View file

@ -4,11 +4,11 @@
<li><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}">{{identityProvider.alias}}</a></li>
<li><a href="#/realms/{{realm.realm}}/identity-provider-mappers/{{identityProvider.alias}}/mappers">Identity Provider Mappers</a></li>
<li class="active" data-ng-show="create">Create IdentityProvider Mapper</li>
<li class="active" data-ng-hide="create">{{mapper.name}}</li>
<li class="active" data-ng-hide="create">{{mapper.name|capitalize}}</li>
</ol>
<h1 data-ng-hide="create"><strong>Identity Provider Mapper</strong> {{mapper.name}}</h1>
<h1 data-ng-show="create"><strong>Add Identity Provider Mapper</strong></h1>
<h1 data-ng-hide="create">{{mapper.name|capitalize}}</h1>
<h1 data-ng-show="create">Add Identity Provider Mapper</h1>
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
<fieldset>

View file

@ -4,7 +4,7 @@
<li>{{identityProvider.alias}}</li>
</ol>
<h1><strong>Identity Provider</strong> {{identityProvider.alias|capitalize}}</h1>
<h1>{{identityProvider.alias|capitalize}}</h1>
<ul class="nav nav-tabs" data-ng-hide="newIdentityProvider">
<li><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}">Settings</a></li>

View file

@ -8,8 +8,8 @@
<li class="active" data-ng-hide="create">{{mapper.name}}</li>
</ol>
<h1 data-ng-show="create"><strong>Create Protocol Mapper</strong></h1>
<h1 data-ng-hide="create"><strong>Protocol Mapper</strong> {{mapper.name}}</h1>
<h1 data-ng-show="create">Create Protocol Mapper</h1>
<h1 data-ng-hide="create">{{mapper.name|capitalize}}</h1>
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">

View file

@ -1,5 +1,5 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<h1><strong>Settings</strong> {{realm.realm|capitalize}}</h1>
<h1>Settings</h1>
<kc-tabs-realm></kc-tabs-realm>

View file

@ -1,5 +1,5 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<h1><strong>Settings</strong> {{realm.realm|capitalize}}</h1>
<h1>Settings</h1>
<kc-tabs-realm></kc-tabs-realm>

View file

@ -1,5 +1,5 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<h1><strong>Roles</strong> {{realm.realm|capitalize}}</h1>
<h1>Roles</h1>
<ul class="nav nav-tabs">
<li><a href="#/realms/{{realm.realm}}/roles">Realm Roles</a></li>

View file

@ -1,5 +1,5 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<h1 data-ng-hide="createRealm"><strong>Settings</strong> {{realm.realm|capitalize}}</h1>
<h1 data-ng-hide="createRealm">Settings</h1>
<h1 data-ng-show="createRealm">Add Realm</h1>
<kc-tabs-realm></kc-tabs-realm>

View file

@ -1,6 +1,6 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<h1>
<span><strong>Admin Events</strong> {{realm.realm|capitalize}}</span>
<span>Admin Events</span>
<kc-tooltip>Displays saved admin events for the realm. Events are related to admin account, for example a realm creation. To enable persisted events go to config.</kc-tooltip>
</h1>

View file

@ -1,6 +1,6 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<h1>
<span><strong>Events Config</strong> {{realm.realm|capitalize}}</span>
<span>Events Config</span>
<kc-tooltip>Displays configuration options to enable persistence of user and admin events.</kc-tooltip>
</h1>
@ -10,7 +10,7 @@
<li data-ng-class="(path[2] == 'events-settings') && 'active'"><a href="#/realms/{{realm.realm}}/events-settings">Config</a></li>
</ul>
<div id="content">
<h2><span>{{realm.realm}}</span> Events Config</h2>
<h2>Events Config</h2>
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageEvents">

View file

@ -1,6 +1,6 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<h1>
<span><strong>Events</strong> {{realm.realm|capitalize}}</span>
<span>Events</span>
<kc-tooltip>Displays saved events for the realm. Events are related to user accounts, for example a user login. To enable persisted events go to config.</kc-tooltip>
</h1>

View file

@ -4,8 +4,8 @@
<li>{{identityProvider.alias}}</li>
</ol>
<h1 data-ng-hide="create"><strong>Identity Provider</strong> {{identityProvider.alias|capitalize}}</h1>
<h1 data-ng-show="create"><strong>Add OpenID Connect Identity Provider</strong></h1>
<h1 data-ng-hide="create">{{identityProvider.alias|capitalize}}</h1>
<h1 data-ng-show="create">Add OpenID Connect Identity Provider</h1>
<ul class="nav nav-tabs" data-ng-hide="newIdentityProvider">
<li><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}">Settings</a></li>

View file

@ -4,8 +4,8 @@
<li>{{identityProvider.alias}}</li>
</ol>
<h1 data-ng-hide="create"><strong>Identity Provider</strong> {{identityProvider.alias|capitalize}}</h1>
<h1 data-ng-show="create"><strong>Add OpenID Connect Identity Provider</strong></h1>
<h1 data-ng-hide="create">{{identityProvider.alias|capitalize}}</h1>
<h1 data-ng-show="create">Add OpenID Connect Identity Provider</h1>
<ul class="nav nav-tabs" data-ng-hide="newIdentityProvider">
<li class="active"><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}">Settings</a></li>

View file

@ -4,8 +4,8 @@
<li>{{identityProvider.alias}}</li>
</ol>
<h1 data-ng-hide="create"><strong>Identity Provider</strong> {{identityProvider.alias|capitalize}}</h1>
<h1 data-ng-show="create"><strong>Add SAML Identity Provider</strong></h1>
<h1 data-ng-hide="create">{{identityProvider.alias|capitalize}}</h1>
<h1 data-ng-show="create">Add SAML Identity Provider</h1>
<ul class="nav nav-tabs" data-ng-hide="newIdentityProvider">
<li class="active"><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}">Settings</a></li>

View file

@ -4,8 +4,8 @@
<li>{{identityProvider.alias}}</li>
</ol>
<h1 data-ng-hide="create"><strong>Identity Provider</strong> {{identityProvider.alias|capitalize}}</h1>
<h1 data-ng-show="create"><strong>Add Social Identity Provider</strong></h1>
<h1 data-ng-hide="create">{{identityProvider.alias|capitalize}}</h1>
<h1 data-ng-show="create">Add Social Identity Provider</h1>
<ul class="nav nav-tabs" data-ng-hide="newIdentityProvider">
<li class="active"><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}">Settings</a></li>

View file

@ -1,7 +1,7 @@
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="clientId">Key <span class="required">*</span></label>
<div class="col-md-6">
<input class="form-control" id="clientId" type="text" ng-model="identityProvider.config.key" required>
</div>
<kc-tooltip>The Key obtained from Stack Overflow client registration.</kc-tooltip>
</div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="clientId">Key <span class="required">*</span></label>
<div class="col-md-6">
<input class="form-control" id="clientId" type="text" ng-model="identityProvider.config.key" required>
</div>
<kc-tooltip>The Key obtained from Stack Overflow client registration.</kc-tooltip>
</div>

View file

@ -1,5 +1,5 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<h1><strong>Identity Providers</strong> {{realm.realm|capitalize}}</h1>
<h1>Identity Providers</h1>
<form name="realmForm" novalidate class="form-horizontal">
<fieldset>

View file

@ -1,5 +1,5 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<h1><strong>Settings</strong> {{realm.realm|capitalize}}</h1>
<h1>Settings</h1>
<kc-tabs-realm></kc-tabs-realm>

View file

@ -1,5 +1,5 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<h1><strong>Settings</strong> {{realm.realm|capitalize}}</h1>
<h1>Settings</h1>
<kc-tabs-realm></kc-tabs-realm>

View file

@ -1,5 +1,5 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<h1><strong>Settings</strong> {{realm.realm|capitalize}}</h1>
<h1>Settings</h1>
<kc-tabs-realm></kc-tabs-realm>

View file

@ -1,5 +1,5 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<h1><strong>Settings</strong> {{realm.realm|capitalize}}</h1>
<h1>Settings</h1>
<kc-tabs-realm></kc-tabs-realm>

View file

@ -1,5 +1,5 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<h1><strong>Settings</strong> {{realm.realm|capitalize}}</h1>
<h1>Settings</h1>
<kc-tabs-realm></kc-tabs-realm>

View file

@ -6,8 +6,8 @@
<li data-ng-show="create">Add Role</li>
</ol>
<h1 data-ng-hide="create"><strong>Role</strong> {{role.name}}</h1>
<h1 data-ng-show="create"><strong>Add Role</strong></h1>
<h1 data-ng-hide="create">{{role.name|capitalize}}</h1>
<h1 data-ng-show="create">Add Role</h1>
<form class="form-horizontal clearfix" name="realmForm" novalidate kc-read-only="!access.manageRealm">
<fieldset>

View file

@ -1,5 +1,5 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<h1><strong>Roles</strong> {{realm.realm|capitalize}}</h1>
<h1>Roles</h1>
<ul class="nav nav-tabs">
<li class="active"><a href="#/realms/{{realm.realm}}/roles">Realm Roles</a></li>

View file

@ -4,7 +4,7 @@
<li>{{user.username}}</li>
</ol>
<h1><strong>User</strong> {{user.username|capitalize}}</h1>
<h1>{{user.username|capitalize}}</h1>
<kc-tabs-user></kc-tabs-user>

View file

@ -1,5 +1,5 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<h1><strong>Sessions</strong> {{realm.realm|capitalize}}</h1>
<h1>Sessions</h1>
<ul class="nav nav-tabs">
<li class="active"><a href="#/realms/{{realm.realm}}/sessions/realm">Realm Sessions</a></li>

View file

@ -1,5 +1,5 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<h1><strong>Sessions</strong> {{realm.realm|capitalize}}</h1>
<h1>Sessions</h1>
<ul class="nav nav-tabs">
<li><a href="#/realms/{{realm.realm}}/sessions/realm">Realm Sessions</a></li>

View file

@ -4,7 +4,7 @@
<li>{{user.username}}</li>
</ol>
<h1><strong>User</strong> {{user.username|capitalize}}</h1>
<h1>{{user.username|capitalize}}</h1>
<kc-tabs-user></kc-tabs-user>

View file

@ -4,7 +4,7 @@
<li>{{user.username}}</li>
</ol>
<h1><strong>User</strong> {{user.username|capitalize}}</h1>
<h1>{{user.username|capitalize}}</h1>
<kc-tabs-user></kc-tabs-user>

View file

@ -5,7 +5,7 @@
<li data-ng-show="create">Add User</li>
</ol>
<h1 data-ng-hide="create"><strong>User</strong> {{user.username|capitalize}}</h1>
<h1 data-ng-hide="create">{{user.username|capitalize}}</h1>
<h1 data-ng-show="create">Add User</h1>
<kc-tabs-user></kc-tabs-user>

View file

@ -4,7 +4,7 @@
<li>{{user.username}}</li>
</ol>
<h1><strong>User</strong> {{user.username|capitalize}}</h1>
<h1>{{user.username|capitalize}}</h1>
<kc-tabs-user></kc-tabs-user>

View file

@ -1,6 +1,6 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<h1>
<span><strong>User Federation</strong> {{realm.realm|capitalize}}</span>
<span>User Federation</span>
</h1>
<table class="table table-striped table-bordered">

View file

@ -1,5 +1,5 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<h1><strong>Users</strong> {{realm.realm|capitalize}}</h1>
<h1>Users</h1>
<table class="table table-striped table-bordered">
<caption data-ng-show="users" class="hidden">Table of realm users</caption>

View file

@ -4,7 +4,7 @@
<li>{{user.username}}</li>
</ol>
<h1><strong>User</strong> {{user.username|capitalize}}</h1>
<h1>{{user.username|capitalize}}</h1>
<kc-tabs-user></kc-tabs-user>

View file

@ -61,53 +61,6 @@
</div>
</div>
<div class="form-group">
<div class="${properties.kcLabelWrapperClass!}">
<label for="user.attributes.street" class="${properties.kcLabelClass!}">${msg("street")}</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input type="text" class="${properties.kcInputClass!}" id="user.attributes.street" name="user.attributes.street"/>
</div>
</div>
<div class="form-group">
<div class="${properties.kcLabelWrapperClass!}">
<label for="user.attributes.locality" class="${properties.kcLabelClass!}">${msg("locality")}</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input type="text" class="${properties.kcInputClass!}" id="user.attributes.locality" name="user.attributes.locality"/>
</div>
</div>
<div class="form-group">
<div class="${properties.kcLabelWrapperClass!}">
<label for="user.attributes.region" class="${properties.kcLabelClass!}">${msg("region")}</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input type="text" class="${properties.kcInputClass!}" id="user.attributes.region" name="user.attributes.region"/>
</div>
</div>
<div class="form-group">
<div class="${properties.kcLabelWrapperClass!}">
<label for="user.attributes.postal_code" class="${properties.kcLabelClass!}">${msg("postal_code")}</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input type="text" class="${properties.kcInputClass!}" id="user.attributes.postal_code" name="user.attributes.postal_code"/>
</div>
</div>
<div class="form-group">
<div class="${properties.kcLabelWrapperClass!}">
<label for="user.attributes.country" class="${properties.kcLabelClass!}">${msg("country")}</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input type="text" class="${properties.kcInputClass!}" id="user.attributes.country" name="user.attributes.country"/>
</div>
</div>
<div class="${properties.kcFormGroupClass!}">
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
<div class="${properties.kcFormOptionsWrapperClass!}">

View file

@ -121,11 +121,6 @@
display: block;
}
#kc-login {
float: right;
margin-left: 10px;
}
#kc-feedback-wrapper {
display: inline-block;
width: auto;

View file

@ -374,11 +374,21 @@ public class SamlService {
for (String sessionIndex : logoutRequest.getSessionIndex()) {
ClientSessionModel clientSession = session.sessions().getClientSession(realm, sessionIndex);
if (clientSession == null) continue;
UserSessionModel userSession = clientSession.getUserSession();
if (clientSession.getClient().getClientId().equals(client.getClientId())) {
// remove requesting client from logout
clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT);
// Remove also other clientSessions of this client as there could be more in this UserSession
if (userSession != null) {
for (ClientSessionModel clientSession2 : userSession.getClientSessions()) {
if (clientSession2.getClient().getId().equals(client.getId())) {
clientSession2.setAction(ClientSessionModel.Action.LOGGED_OUT);
}
}
}
}
UserSessionModel userSession = clientSession.getUserSession();
try {
authManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
} catch (Exception e) {

View file

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

@ -3,10 +3,11 @@ package org.keycloak.social.github;
import org.codehaus.jackson.JsonNode;
import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
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.IdentityBrokerException;
import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.social.SocialIdentityProvider;
/**
@ -14,40 +15,42 @@ import org.keycloak.social.SocialIdentityProvider;
*/
public class GitHubIdentityProvider extends AbstractOAuth2IdentityProvider implements SocialIdentityProvider {
public static final String AUTH_URL = "https://github.com/login/oauth/authorize";
public static final String TOKEN_URL = "https://github.com/login/oauth/access_token";
public static final String PROFILE_URL = "https://api.github.com/user";
public static final String DEFAULT_SCOPE = "user:email";
public static final String AUTH_URL = "https://github.com/login/oauth/authorize";
public static final String TOKEN_URL = "https://github.com/login/oauth/access_token";
public static final String PROFILE_URL = "https://api.github.com/user";
public static final String DEFAULT_SCOPE = "user:email";
public GitHubIdentityProvider(OAuth2IdentityProviderConfig config) {
super(config);
config.setAuthorizationUrl(AUTH_URL);
config.setTokenUrl(TOKEN_URL);
config.setUserInfoUrl(PROFILE_URL);
}
public GitHubIdentityProvider(OAuth2IdentityProviderConfig config) {
super(config);
config.setAuthorizationUrl(AUTH_URL);
config.setTokenUrl(TOKEN_URL);
config.setUserInfoUrl(PROFILE_URL);
}
@Override
protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) {
try {
JsonNode profile = JsonSimpleHttp.asJson(SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken));
@Override
protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) {
try {
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 = getJsonProperty(profile, "login");
user.setUsername(username);
user.setName(getJsonProperty(profile, "name"));
user.setEmail(getJsonProperty(profile, "email"));
user.setIdpConfig(getConfig());
user.setIdp(this);
String username = getJsonProperty(profile, "login");
user.setUsername(username);
user.setName(getJsonProperty(profile, "name"));
user.setEmail(getJsonProperty(profile, "email"));
user.setIdpConfig(getConfig());
user.setIdp(this);
return user;
} catch (Exception e) {
throw new IdentityBrokerException("Could not obtain user profile from github.", e);
}
}
AbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile, getConfig().getAlias());
@Override
protected String getDefaultScopes() {
return DEFAULT_SCOPE;
}
return user;
} catch (Exception e) {
throw new IdentityBrokerException("Could not obtain user profile from github.", 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.github;
import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
/**
* User attribute mapper.
*
* @author Vlastimil Elias (velias at redhat dot com)
*/
public class GitHubUserAttributeMapper extends AbstractJsonUserAttributeMapper {
private static final String[] cp = new String[] { GitHubIdentityProviderFactory.PROVIDER_ID };
@Override
public String[] getCompatibleProviders() {
return cp;
}
@Override
public String getId() {
return "github-user-attribute-mapper";
}
}

View file

@ -0,0 +1 @@
org.keycloak.social.github.GitHubUserAttributeMapper

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.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
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.IdentityBrokerException;
import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.social.SocialIdentityProvider;
/**
@ -58,16 +59,18 @@ public class LinkedInIdentityProvider extends AbstractOAuth2IdentityProvider imp
try {
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"));
user.setUsername(username);
String username = extractUsernameFromProfileURL(getJsonProperty(profile, "publicProfileUrl"));
user.setUsername(username);
user.setName(getJsonProperty(profile, "formattedName"));
user.setEmail(getJsonProperty(profile, "emailAddress"));
user.setIdpConfig(getConfig());
user.setIdp(this);
user.setIdpConfig(getConfig());
user.setIdp(this);
return user;
AbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile, getConfig().getAlias());
return user;
} catch (Exception 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.jboss.logging.Logger;
import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
import org.keycloak.broker.oidc.util.JsonSimpleHttp;
import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.social.SocialIdentityProvider;
/**
@ -37,8 +38,7 @@ import org.keycloak.social.SocialIdentityProvider;
*
* @author Vlastimil Elias (velias at redhat dot com)
*/
public class StackoverflowIdentityProvider extends AbstractOAuth2IdentityProvider<StackOverflowIdentityProviderConfig>
implements SocialIdentityProvider<StackOverflowIdentityProviderConfig> {
public class StackoverflowIdentityProvider extends AbstractOAuth2IdentityProvider<StackOverflowIdentityProviderConfig> implements SocialIdentityProvider<StackOverflowIdentityProviderConfig> {
private static final Logger log = Logger.getLogger(StackoverflowIdentityProvider.class);
@ -54,8 +54,6 @@ public class StackoverflowIdentityProvider extends AbstractOAuth2IdentityProvide
config.setUserInfoUrl(PROFILE_URL);
}
@Override
protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) {
log.debug("doGetFederatedIdentity()");
@ -67,18 +65,19 @@ public class StackoverflowIdentityProvider extends AbstractOAuth2IdentityProvide
}
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"));
user.setUsername(username);
String username = extractUsernameFromProfileURL(getJsonProperty(profile, "link"));
user.setUsername(username);
user.setName(unescapeHtml3(getJsonProperty(profile, "display_name")));
// email is not provided
// user.setEmail(getJsonProperty(profile, "email"));
user.setIdpConfig(getConfig());
user.setIdp(this);
user.setIdpConfig(getConfig());
user.setIdp(this);
AbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile, getConfig().getAlias());
return user;
return user;
} catch (Exception 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

View file

@ -45,6 +45,11 @@ public class SAMLKeyCloakServerBrokerWithSignatureTest extends AbstractIdentityP
}
};
// @Test
public void testSleep() throws Exception {
Thread.sleep(100000000);
}
@Override
protected String getProviderId() {
return "kc-saml-signed-idp";

View file

@ -247,7 +247,7 @@ public class FederationProvidersIntegrationTest {
loginPage.clickRegister();
registerPage.assertCurrent();
registerPage.register("firstName", "lastName", "email2@check.cz", "registerUserSuccess2", "Password1", "Password1", "non-LDAP-Mapped street", null, null, "78910", null);
registerPage.register("firstName", "lastName", "email2@check.cz", "registerUserSuccess2", "Password1", "Password1");
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
KeycloakSession session = keycloakRule.startSession();
@ -257,8 +257,6 @@ public class FederationProvidersIntegrationTest {
Assert.assertNotNull(user);
Assert.assertNotNull(user.getFederationLink());
Assert.assertEquals(user.getFederationLink(), ldapModel.getId());
Assert.assertEquals("78910", user.getAttribute("postal_code"));
Assert.assertEquals("non-LDAP-Mapped street", user.getAttribute("street"));
} finally {
keycloakRule.stopSession(session, false);
}

View file

@ -57,25 +57,6 @@ public class RegisterPage extends AbstractPage {
@FindBy(className = "feedback-error")
private WebElement loginErrorMessage;
public void register(String firstName, String lastName, String email, String username, String password, String passwordConfirm,
String street, String cityOrLocality, String stateOrRegion, String zipOrPostalCode, String country) {
fillExtendedField("street", street);
fillExtendedField("locality", cityOrLocality);
fillExtendedField("region", stateOrRegion);
fillExtendedField("postal_code", zipOrPostalCode);
fillExtendedField("country", country);
register(firstName, lastName, email, username, password, passwordConfirm);
}
private void fillExtendedField(String fieldName, String value) {
WebElement field = driver.findElement(By.id("user.attributes." + fieldName));
field.clear();
if (value != null) {
field.sendKeys(value);
}
}
public void register(String firstName, String lastName, String email, String username, String password, String passwordConfirm) {
firstNameInput.clear();
if (firstName != null) {