keycloak-memoizeit

merge

9/11/2013 10:44:36 AM

Changes

pom.xml 2(+1 -1)

Details

diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index e365a61..92c3f10 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -19,6 +19,7 @@ public class RealmRepresentation {
     protected boolean cookieLoginAllowed;
     protected boolean registrationAllowed;
     protected boolean social;
+    protected boolean automaticRegistrationAfterSocialLogin;
     protected String privateKey;
     protected String publicKey;
     protected List<RoleRepresentation> roles;
@@ -29,6 +30,7 @@ public class RealmRepresentation {
     protected List<UserRepresentation> users;
     protected List<RoleMappingRepresentation> roleMappings;
     protected List<ScopeMappingRepresentation> scopeMappings;
+    protected List<SocialMappingRepresentation> socialMappings;
     protected List<ApplicationRepresentation> applications;
 
 
@@ -144,6 +146,18 @@ public class RealmRepresentation {
         return mapping;
     }
 
+    public List<SocialMappingRepresentation> getSocialMappings() {
+        return socialMappings;
+    }
+
+    public SocialMappingRepresentation socialMapping(String username) {
+        SocialMappingRepresentation mapping = new SocialMappingRepresentation();
+        mapping.setUsername(username);
+        if (socialMappings == null) socialMappings = new ArrayList<SocialMappingRepresentation>();
+        socialMappings.add(mapping);
+        return mapping;
+    }
+
     public Set<String> getRequiredCredentials() {
         return requiredCredentials;
     }
@@ -223,4 +237,12 @@ public class RealmRepresentation {
     public void setSocial(boolean social) {
         this.social = social;
     }
+
+    public boolean isAutomaticRegistrationAfterSocialLogin() {
+        return automaticRegistrationAfterSocialLogin;
+    }
+
+    public void setAutomaticRegistrationAfterSocialLogin(boolean automaticRegistrationAfterSocialLogin) {
+        this.automaticRegistrationAfterSocialLogin = automaticRegistrationAfterSocialLogin;
+    }
 }
diff --git a/core/src/main/java/org/keycloak/representations/idm/SocialLinkRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/SocialLinkRepresentation.java
new file mode 100644
index 0000000..a6e1838
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/SocialLinkRepresentation.java
@@ -0,0 +1,26 @@
+package org.keycloak.representations.idm;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class SocialLinkRepresentation {
+
+    protected String socialProvider;
+    protected String socialUsername;
+
+    public String getSocialProvider() {
+        return socialProvider;
+    }
+
+    public void setSocialProvider(String socialProvider) {
+        this.socialProvider = socialProvider;
+    }
+
+    public String getSocialUsername() {
+        return socialUsername;
+    }
+
+    public void setSocialUsername(String socialUsername) {
+        this.socialUsername = socialUsername;
+    }
+}
diff --git a/core/src/main/java/org/keycloak/representations/idm/SocialMappingRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/SocialMappingRepresentation.java
new file mode 100644
index 0000000..57dd874
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/SocialMappingRepresentation.java
@@ -0,0 +1,43 @@
+package org.keycloak.representations.idm;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class SocialMappingRepresentation {
+
+    protected String self; // link
+    protected String username;
+    protected List<SocialLinkRepresentation> socialLinks;
+
+    public String getSelf() {
+        return self;
+    }
+
+    public void setSelf(String self) {
+        this.self = self;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public List<SocialLinkRepresentation> getSocialLinks() {
+        return socialLinks;
+    }
+
+    public SocialLinkRepresentation socialLink(String socialProvider, String socialUsername) {
+        SocialLinkRepresentation link = new SocialLinkRepresentation();
+        link.setSocialProvider(socialProvider);
+        link.setSocialUsername(socialUsername);
+        if (socialLinks == null) socialLinks = new ArrayList<SocialLinkRepresentation>();
+        socialLinks.add(link);
+        return link;
+    }
+}
diff --git a/examples/as7-eap-demo/server/src/main/resources/META-INF/testrealm.json b/examples/as7-eap-demo/server/src/main/resources/META-INF/testrealm.json
index f92e34c..bb918ef 100755
--- a/examples/as7-eap-demo/server/src/main/resources/META-INF/testrealm.json
+++ b/examples/as7-eap-demo/server/src/main/resources/META-INF/testrealm.json
@@ -6,6 +6,8 @@
     "sslNotRequired": true,
     "cookieLoginAllowed": true,
     "registrationAllowed": true,
+    "social": true,
+    "automaticRegistrationAfterSocialLogin": false,
     "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
     "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
     "requiredCredentials": [ "password" ],
diff --git a/forms/src/main/java/org/keycloak/forms/RegisterBean.java b/forms/src/main/java/org/keycloak/forms/RegisterBean.java
index 60d247a..77d6a8c 100755
--- a/forms/src/main/java/org/keycloak/forms/RegisterBean.java
+++ b/forms/src/main/java/org/keycloak/forms/RegisterBean.java
@@ -26,6 +26,7 @@ import java.util.Map;
 
 import javax.annotation.PostConstruct;
 import javax.faces.bean.ManagedBean;
+import javax.faces.bean.ManagedProperty;
 import javax.faces.bean.RequestScoped;
 import javax.faces.context.FacesContext;
 import javax.servlet.http.HttpServletRequest;
@@ -42,6 +43,8 @@ public class RegisterBean {
 
     private HashMap<String, String> formData;
 
+    private boolean socialRegistration;
+
     @PostConstruct
     public void init() {
         FacesContext ctx = FacesContext.getCurrentInstance();
@@ -49,6 +52,9 @@ public class RegisterBean {
 
         this.formData = new HashMap<String, String>();
 
+        Boolean socialRegistrationAttr = (Boolean)request.getAttribute(FormFlows.SOCIAL_REGISTRATION);
+        this.socialRegistration = socialRegistrationAttr != null && socialRegistrationAttr;
+
         @SuppressWarnings("unchecked")
         MultivaluedMap<String, String> formData = (MultivaluedMap<String, String>) request.getAttribute(FormFlows.DATA);
         if (formData != null) {
@@ -62,4 +68,8 @@ public class RegisterBean {
         return formData;
     }
 
+    public boolean isSocialRegistration() {
+        return socialRegistration;
+    }
+
 }
diff --git a/forms/src/main/java/org/keycloak/forms/SocialBean.java b/forms/src/main/java/org/keycloak/forms/SocialBean.java
index b007083..fab91b1 100644
--- a/forms/src/main/java/org/keycloak/forms/SocialBean.java
+++ b/forms/src/main/java/org/keycloak/forms/SocialBean.java
@@ -46,6 +46,9 @@ public class SocialBean {
     @ManagedProperty(value = "#{realm}")
     private RealmBean realm;
 
+    @ManagedProperty(value = "#{register}")
+    private RegisterBean registerBean;
+
     @ManagedProperty(value = "#{url}")
     private UrlBean url;
 
@@ -73,4 +76,32 @@ public class SocialBean {
         return providers;
     }
 
+    // Display panel with social providers just in case that social is enabled for realm, but we are not in the middle of registration with social
+    public boolean isDisplaySocialProviders() {
+        return realm.isSocial() && !registerBean.isSocialRegistration();
+    }
+
+    public RealmBean getRealm() {
+        return realm;
+    }
+
+    public void setRealm(RealmBean realm) {
+        this.realm = realm;
+    }
+
+    public UrlBean getUrl() {
+        return url;
+    }
+
+    public void setUrl(UrlBean url) {
+        this.url = url;
+    }
+
+    public RegisterBean getRegisterBean() {
+        return registerBean;
+    }
+
+    public void setRegisterBean(RegisterBean registerBean) {
+        this.registerBean = registerBean;
+    }
 }
diff --git a/forms/src/main/java/org/keycloak/forms/UrlBean.java b/forms/src/main/java/org/keycloak/forms/UrlBean.java
index ba00609..62017d0 100644
--- a/forms/src/main/java/org/keycloak/forms/UrlBean.java
+++ b/forms/src/main/java/org/keycloak/forms/UrlBean.java
@@ -45,6 +45,9 @@ public class UrlBean {
     @ManagedProperty(value = "#{realm}")
     private RealmBean realm;
 
+    @ManagedProperty(value = "#{register}")
+    private RegisterBean registerBean;
+
     @PostConstruct
     public void init() {
         FacesContext ctx = FacesContext.getCurrentInstance();
@@ -64,6 +67,14 @@ public class UrlBean {
         this.realm = realm;
     }
 
+    public RegisterBean getRegisterBean() {
+        return registerBean;
+    }
+
+    public void setRegisterBean(RegisterBean registerBean) {
+        this.registerBean = registerBean;
+    }
+
     public String getAccessUrl() {
         return Urls.accountAccessPage(baseURI, realm.getId()).toString();
     }
@@ -98,7 +109,10 @@ public class UrlBean {
 
     public String getRegistrationAction() {
         if (realm.isSaas()) {
+            // TODO: saas social registration
             return Urls.saasRegisterAction(baseURI).toString();
+        } else if (registerBean.isSocialRegistration()) {
+            return Urls.socialRegisterAction(baseURI, realm.getId()).toString();
         } else {
             return Urls.realmRegisterAction(baseURI, realm.getId()).toString();
         }
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/register.xhtml b/forms/src/main/resources/META-INF/resources/forms/theme/default/register.xhtml
index 0078271..ca9d8b6 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/register.xhtml
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/register.xhtml
@@ -39,6 +39,8 @@
 	</ui:define>
 	
 	<ui:define name="info">
-		<p>#{messages.alreadyHaveAccount} <a href="#{url.loginUrl}">#{messages.logIn}</a>.</p>
+        <h:panelGroup rendered="#{not register.socialRegistration}">
+            <p>#{messages.alreadyHaveAccount} <a href="#{url.loginUrl}">#{messages.logIn}</a>.</p>
+        </h:panelGroup>
 	</ui:define>
 </ui:composition>
\ No newline at end of file
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login.xhtml b/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login.xhtml
index 05ee4fb..e99a442 100644
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login.xhtml
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login.xhtml
@@ -39,12 +39,12 @@ body {
 						</div>
 				</h:panelGroup>
 
-				<h:panelGroup rendered="#{realm.social}">
+				<h:panelGroup rendered="#{social.displaySocialProviders}">
 					<section class="social-login"> <span>or</span>
 					<h3>Social login area</h3>
 					<p>#{messages.logInWith}</p>
 					<ul>
-						<ui:repeat var="p" value="#{forms.providers}">
+						<ui:repeat var="p" value="#{social.providers}">
 							<li><a href="#{p.loginUrl}" class="zocial #{p.id}"> <span class="text">#{p.name}</span></a></li>
 						</ui:repeat>
 					</ul>

pom.xml 2(+1 -1)

diff --git a/pom.xml b/pom.xml
index e0c89cf..6310d17 100755
--- a/pom.xml
+++ b/pom.xml
@@ -236,7 +236,7 @@
 			<dependency>
 				<groupId>org.jboss.arquillian.extension</groupId>
 				<artifactId>arquillian-drone-bom</artifactId>
-				<version>1.2.0.Alpha3</version>
+				<version>1.2.0.Beta1</version>
 				<type>pom</type>
 				<scope>import</scope>
 			</dependency>
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index 997ab2c..cdbed7f 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -74,6 +74,7 @@ public class RealmManager {
         realm.setSocial(rep.isSocial());
         realm.setCookieLoginAllowed(rep.isCookieLoginAllowed());
         realm.setRegistrationAllowed(rep.isRegistrationAllowed());
+        realm.setAutomaticRegistrationAfterSocialLogin(rep.isAutomaticRegistrationAfterSocialLogin());
         realm.setSslNotRequired((rep.isSslNotRequired()));
         realm.setAccessCodeLifespan(rep.getAccessCodeLifespan());
         realm.setTokenLifespan(rep.getTokenLifespan());
@@ -109,6 +110,7 @@ public class RealmManager {
         newRealm.setSslNotRequired(rep.isSslNotRequired());
         newRealm.setCookieLoginAllowed(rep.isCookieLoginAllowed());
         newRealm.setRegistrationAllowed(rep.isRegistrationAllowed());
+        newRealm.setAutomaticRegistrationAfterSocialLogin(rep.isAutomaticRegistrationAfterSocialLogin());
         if (rep.getPrivateKey() == null || rep.getPublicKey() == null) {
             generateRealmKeys(newRealm);
         } else {
@@ -185,6 +187,16 @@ public class RealmManager {
 
             }
         }
+
+        if (rep.getSocialMappings() != null) {
+            for (SocialMappingRepresentation socialMapping : rep.getSocialMappings()) {
+                UserModel user = userMap.get(socialMapping.getUsername());
+                for (SocialLinkRepresentation link : socialMapping.getSocialLinks()) {
+                    SocialLinkModel mappingModel = new SocialLinkModel(link.getSocialProvider(), link.getSocialUsername());
+                    newRealm.addSocialLink(user, mappingModel);
+                }
+            }
+        }
     }
 
     public void createRole(RealmModel newRealm, RoleRepresentation roleRep) {
@@ -245,6 +257,7 @@ public class RealmManager {
         rep.setRealm(realm.getName());
         rep.setEnabled(realm.isEnabled());
         rep.setSocial(realm.isSocial());
+        rep.setAutomaticRegistrationAfterSocialLogin(realm.isAutomaticRegistrationAfterSocialLogin());
         rep.setSslNotRequired(realm.isSslNotRequired());
         rep.setCookieLoginAllowed(realm.isCookieLoginAllowed());
         rep.setPublicKey(realm.getPublicKeyPem());
diff --git a/services/src/main/java/org/keycloak/services/models/picketlink/mappings/RealmData.java b/services/src/main/java/org/keycloak/services/models/picketlink/mappings/RealmData.java
index 860ea7e..d95e7ea 100755
--- a/services/src/main/java/org/keycloak/services/models/picketlink/mappings/RealmData.java
+++ b/services/src/main/java/org/keycloak/services/models/picketlink/mappings/RealmData.java
@@ -14,6 +14,7 @@ public class RealmData extends AbstractPartition {
     private boolean cookieLoginAllowed;
     private boolean registrationAllowed;
     private boolean social;
+    private boolean automaticRegistrationAfterSocialLogin;
     private int tokenLifespan;
     private int accessCodeLifespan;
     private String publicKeyPem;
@@ -55,6 +56,15 @@ public class RealmData extends AbstractPartition {
     }
 
     @AttributeProperty
+    public boolean isAutomaticRegistrationAfterSocialLogin() {
+        return automaticRegistrationAfterSocialLogin;
+    }
+
+    public void setAutomaticRegistrationAfterSocialLogin(boolean automaticRegistrationAfterSocialLogin) {
+        this.automaticRegistrationAfterSocialLogin = automaticRegistrationAfterSocialLogin;
+    }
+
+    @AttributeProperty
     public boolean isSslNotRequired() {
         return sslNotRequired;
     }
diff --git a/services/src/main/java/org/keycloak/services/models/picketlink/mappings/RealmEntity.java b/services/src/main/java/org/keycloak/services/models/picketlink/mappings/RealmEntity.java
index e7e0883..0d64e86 100755
--- a/services/src/main/java/org/keycloak/services/models/picketlink/mappings/RealmEntity.java
+++ b/services/src/main/java/org/keycloak/services/models/picketlink/mappings/RealmEntity.java
@@ -37,6 +37,8 @@ public class RealmEntity implements Serializable {
     @AttributeValue
     private boolean social;
     @AttributeValue
+    private boolean automaticRegistrationAfterSocialLogin;
+    @AttributeValue
     private int tokenLifespan;
     @AttributeValue
     private int accessCodeLifespan;
@@ -106,6 +108,14 @@ public class RealmEntity implements Serializable {
         this.social = social;
     }
 
+    public boolean isAutomaticRegistrationAfterSocialLogin() {
+        return automaticRegistrationAfterSocialLogin;
+    }
+
+    public void setAutomaticRegistrationAfterSocialLogin(boolean automaticRegistrationAfterSocialLogin) {
+        this.automaticRegistrationAfterSocialLogin = automaticRegistrationAfterSocialLogin;
+    }
+
     public int getTokenLifespan() {
         return tokenLifespan;
     }
diff --git a/services/src/main/java/org/keycloak/services/models/picketlink/RealmAdapter.java b/services/src/main/java/org/keycloak/services/models/picketlink/RealmAdapter.java
index 2b76984..4641f09 100755
--- a/services/src/main/java/org/keycloak/services/models/picketlink/RealmAdapter.java
+++ b/services/src/main/java/org/keycloak/services/models/picketlink/RealmAdapter.java
@@ -5,15 +5,27 @@ import org.jboss.resteasy.logging.Logger;
 import org.jboss.resteasy.security.PemUtils;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.services.managers.RealmManager;
-import org.keycloak.services.models.*;
-import org.keycloak.services.models.picketlink.mappings.ApplicationData;
+import org.keycloak.services.models.KeycloakSession;
+import org.keycloak.services.models.RealmModel;
+import org.keycloak.services.models.RequiredCredentialModel;
+import org.keycloak.services.models.ApplicationModel;
+import org.keycloak.services.models.RoleModel;
+import org.keycloak.services.models.SocialLinkModel;
+import org.keycloak.services.models.UserCredentialModel;
+import org.keycloak.services.models.UserModel;
 import org.keycloak.services.models.picketlink.mappings.RealmData;
+import org.keycloak.services.models.picketlink.mappings.ApplicationData;
 import org.keycloak.services.models.picketlink.relationships.*;
+import org.keycloak.services.models.picketlink.relationships.RequiredApplicationCredentialRelationship;
 import org.picketlink.idm.IdentityManager;
 import org.picketlink.idm.PartitionManager;
 import org.picketlink.idm.RelationshipManager;
-import org.picketlink.idm.credential.*;
-import org.picketlink.idm.model.AttributedType;
+import org.picketlink.idm.credential.Credentials;
+import org.picketlink.idm.credential.Password;
+import org.picketlink.idm.credential.TOTPCredential;
+import org.picketlink.idm.credential.TOTPCredentials;
+import org.picketlink.idm.credential.UsernamePasswordCredentials;
+import org.picketlink.idm.credential.X509CertificateCredentials;
 import org.picketlink.idm.model.IdentityType;
 import org.picketlink.idm.model.sample.Grant;
 import org.picketlink.idm.model.sample.Role;
@@ -27,7 +39,13 @@ import java.io.StringWriter;
 import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.cert.X509Certificate;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * Meant to be a per-request object
@@ -105,6 +123,17 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public boolean isAutomaticRegistrationAfterSocialLogin() {
+        return realm.isAutomaticRegistrationAfterSocialLogin();
+    }
+
+    @Override
+    public void setAutomaticRegistrationAfterSocialLogin(boolean automaticRegistrationAfterSocialLogin) {
+        realm.setAutomaticRegistrationAfterSocialLogin(automaticRegistrationAfterSocialLogin);
+        updateRealm();
+    }
+
+    @Override
     public boolean isSslNotRequired() {
         return realm.isSslNotRequired();
     }
@@ -460,21 +489,6 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
-    public List<UserModel> queryUsers(Map<String, String> parameters) {
-        IdentityQuery<User> userQuery = getIdm().createIdentityQuery(User.class);
-        for (Map.Entry<String, String> entry : parameters.entrySet()) {
-            userQuery.setParameter(AttributedType.QUERY_ATTRIBUTE.byName(entry.getKey()), entry.getValue());
-        }
-        List<User> users = userQuery.getResultList();
-        List<UserModel> userModels = new ArrayList<UserModel>();
-        for (User user : users) {
-            userModels.add(new UserAdapter(user, getIdm()));
-        }
-        return userModels;
-
-    }
-
-    @Override
     public RoleAdapter getRole(String name) {
         Role role = SampleModel.getRole(getIdm(), name);
         if (role == null) return null;
@@ -691,4 +705,54 @@ public class RealmAdapter implements RealmModel {
         realm.setDefaultRoles(defaultRoles);
         updateRealm();
     }
+
+    @Override
+    public UserModel getUserBySocialLink(SocialLinkModel socialLink) {
+        RelationshipQuery<SocialLinkRelationship> query = getRelationshipManager().createRelationshipQuery(SocialLinkRelationship.class);
+        query.setParameter(SocialLinkRelationship.SOCIAL_PROVIDER, socialLink.getSocialProvider());
+        query.setParameter(SocialLinkRelationship.SOCIAL_USERNAME, socialLink.getSocialUsername());
+        List<SocialLinkRelationship> results = query.getResultList();
+        if (results.isEmpty()) {
+            return null;
+        } else if (results.size() > 1) {
+            throw new IllegalStateException("More results found for socialProvider=" + socialLink.getSocialProvider() +
+                    ", socialUsername=" + socialLink.getSocialUsername() + ", results=" + results);
+        } else {
+            User user = results.get(0).getUser();
+            return new UserAdapter(user, getIdm());
+        }
+    }
+
+    @Override
+    public Set<SocialLinkModel> getSocialLinks(UserModel user) {
+        RelationshipQuery<SocialLinkRelationship> query = getRelationshipManager().createRelationshipQuery(SocialLinkRelationship.class);
+        query.setParameter(SocialLinkRelationship.USER, ((UserAdapter)user).getUser());
+        List<SocialLinkRelationship> plSocialLinks = query.getResultList();
+
+        Set<SocialLinkModel> results = new HashSet<SocialLinkModel>();
+        for (SocialLinkRelationship relationship : plSocialLinks) {
+            results.add(new SocialLinkModel(relationship.getSocialProvider(), relationship.getSocialUsername()));
+        }
+        return results;
+    }
+
+    @Override
+    public void addSocialLink(UserModel user, SocialLinkModel socialLink) {
+        SocialLinkRelationship relationship = new SocialLinkRelationship();
+        relationship.setUser(((UserAdapter)user).getUser());
+        relationship.setSocialProvider(socialLink.getSocialProvider());
+        relationship.setSocialUsername(socialLink.getSocialUsername());
+
+        getRelationshipManager().add(relationship);
+    }
+
+    @Override
+    public void removeSocialLink(UserModel user, SocialLinkModel socialLink) {
+        SocialLinkRelationship relationship = new SocialLinkRelationship();
+        relationship.setUser(((UserAdapter)user).getUser());
+        relationship.setSocialProvider(socialLink.getSocialProvider());
+        relationship.setSocialUsername(socialLink.getSocialUsername());
+
+        getRelationshipManager().remove(relationship);
+    }
 }
diff --git a/services/src/main/java/org/keycloak/services/models/picketlink/relationships/SocialLinkRelationship.java b/services/src/main/java/org/keycloak/services/models/picketlink/relationships/SocialLinkRelationship.java
new file mode 100644
index 0000000..c3a9389
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/picketlink/relationships/SocialLinkRelationship.java
@@ -0,0 +1,57 @@
+package org.keycloak.services.models.picketlink.relationships;
+
+import org.picketlink.idm.model.AbstractAttributedType;
+import org.picketlink.idm.model.Attribute;
+import org.picketlink.idm.model.Relationship;
+import org.picketlink.idm.model.sample.User;
+import org.picketlink.idm.query.AttributeParameter;
+import org.picketlink.idm.query.RelationshipQueryParameter;
+
+/**
+ * Binding between user and his social username for particular Social provider
+ *
+ * Example: Keycloak user "john" has username "john123" in social provider "facebook"
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class SocialLinkRelationship extends AbstractAttributedType implements Relationship {
+
+    private static final long serialVersionUID = 154879L;
+
+    public static final AttributeParameter SOCIAL_PROVIDER = new AttributeParameter("socialProvider");
+    public static final AttributeParameter SOCIAL_USERNAME = new AttributeParameter("socialUsername");
+
+    public static final RelationshipQueryParameter USER = new RelationshipQueryParameter() {
+
+        @Override
+        public String getName() {
+            return "user";
+        }
+    };
+
+    private User user;
+
+    public User getUser() {
+        return user;
+    }
+
+    public void setUser(User user) {
+        this.user = user;
+    }
+
+    public String getSocialProvider() {
+        return (String)getAttribute("socialProvider").getValue();
+    }
+
+    public void setSocialProvider(String socialProvider) {
+        setAttribute(new Attribute<String>("socialProvider", socialProvider));
+    }
+
+    public String getSocialUsername() {
+        return (String)getAttribute("socialUsername").getValue();
+    }
+
+    public void setSocialUsername(String socialProviderUserId) {
+        setAttribute(new Attribute<String>("socialUsername", socialProviderUserId));
+    }
+}
diff --git a/services/src/main/java/org/keycloak/services/models/RealmModel.java b/services/src/main/java/org/keycloak/services/models/RealmModel.java
index b954708..9a1f3e5 100755
--- a/services/src/main/java/org/keycloak/services/models/RealmModel.java
+++ b/services/src/main/java/org/keycloak/services/models/RealmModel.java
@@ -127,9 +127,19 @@ public interface RealmModel {
 
     void updateRequiredApplicationCredentials(Set<String> creds);
 
+    UserModel getUserBySocialLink(SocialLinkModel socialLink);
+
+    Set<SocialLinkModel> getSocialLinks(UserModel user);
+
+    void addSocialLink(UserModel user, SocialLinkModel socialLink);
+
+    void removeSocialLink(UserModel user, SocialLinkModel socialLink);
+
     boolean isSocial();
 
     void setSocial(boolean social);
 
-    List<UserModel> queryUsers(Map<String, String> parameters);
+    public boolean isAutomaticRegistrationAfterSocialLogin();
+
+    public void setAutomaticRegistrationAfterSocialLogin(boolean automaticRegistrationAfterSocialLogin);
 }
diff --git a/services/src/main/java/org/keycloak/services/models/SocialLinkModel.java b/services/src/main/java/org/keycloak/services/models/SocialLinkModel.java
new file mode 100644
index 0000000..7a92d8d
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/SocialLinkModel.java
@@ -0,0 +1,31 @@
+package org.keycloak.services.models;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class SocialLinkModel {
+
+    private String socialUsername;
+    private String socialProvider;
+
+    public SocialLinkModel(String socialProvider, String socialUsername) {
+        this.socialUsername = socialUsername;
+        this.socialProvider = socialProvider;
+    }
+
+    public String getSocialUsername() {
+        return socialUsername;
+    }
+
+    public void setSocialUsername(String socialUsername) {
+        this.socialUsername = socialUsername;
+    }
+
+    public String getSocialProvider() {
+        return socialProvider;
+    }
+
+    public void setSocialProvider(String socialProvider) {
+        this.socialProvider = socialProvider;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java b/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java
index 49590fd..bbf453e 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java
@@ -38,6 +38,7 @@ public class FormFlows {
     public static final String ERROR_MESSAGE = "KEYCLOAK_FORMS_ERROR_MESSAGE";
     public static final String REALM = Realm.class.getName();
     public static final String USER = UserModel.class.getName();
+    public static final String SOCIAL_REGISTRATION = "socialRegistration";
 
     private String error;
     private MultivaluedMap<String, String> formData;
@@ -47,6 +48,8 @@ public class FormFlows {
     private HttpRequest request;
     private UserModel userModel;
 
+    private boolean socialRegistration;
+
     FormFlows(RealmModel realm, HttpRequest request) {
         this.realm = realm;
         this.request = request;
@@ -75,6 +78,8 @@ public class FormFlows {
             request.setAttribute(USER, userModel);
         }
 
+        request.setAttribute(SOCIAL_REGISTRATION, socialRegistration);
+
         request.forward(form);
         return null;
     }
@@ -113,6 +118,12 @@ public class FormFlows {
         return this;
     }
 
+    // Set flag whether user registration is triggered from social login
+    public FormFlows setSocialRegistration(boolean socialRegistration) {
+        this.socialRegistration = socialRegistration;
+        return this;
+    }
+
     public FormFlows setFormData(MultivaluedMap<String, String> formData) {
         this.formData = formData;
         return this;
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/Urls.java b/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
index a380041..62b9e56 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
@@ -95,7 +95,7 @@ public class Urls {
         return saasBase(baseUri).path(SaasService.class, "registerPage").build();
     }
 
-    private static UriBuilder socialBase(URI baseUri) {
+    public static UriBuilder socialBase(URI baseUri) {
         return UriBuilder.fromUri(baseUri).path(SocialResource.class);
     }
 
@@ -111,4 +111,8 @@ public class Urls {
     private static UriBuilder tokenBase(URI baseUri) {
         return realmBase(baseUri).path(RealmsResource.class, "getTokenService");
     }
+
+    public static URI socialRegisterAction(URI baseUri, String realmId) {
+        return socialBase(baseUri).path(SocialResource.class, "socialRegistration").build(realmId);
+    }
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/SocialResource.java b/services/src/main/java/org/keycloak/services/resources/SocialResource.java
index 1bcac8b..b260262 100755
--- a/services/src/main/java/org/keycloak/services/resources/SocialResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/SocialResource.java
@@ -21,36 +21,56 @@
  */
 package org.keycloak.services.resources;
 
-import org.jboss.resteasy.logging.Logger;
-import org.jboss.resteasy.spi.HttpRequest;
-import org.keycloak.services.managers.AuthenticationManager;
-import org.keycloak.services.managers.RealmManager;
-import org.keycloak.services.managers.TokenManager;
-import org.keycloak.services.models.KeycloakSession;
-import org.keycloak.services.models.RealmModel;
-import org.keycloak.services.models.RoleModel;
-import org.keycloak.services.models.UserModel;
-import org.keycloak.services.resources.flows.Flows;
-import org.keycloak.services.resources.flows.OAuthFlows;
-import org.keycloak.services.resources.flows.Urls;
-import org.keycloak.social.*;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.UUID;
 
 import javax.imageio.spi.ServiceRegistry;
+import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.QueryParam;
+import javax.ws.rs.container.ResourceContext;
 import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Cookie;
 import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.NewCookie;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.UriInfo;
-import java.net.URISyntaxException;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
+
+import org.jboss.resteasy.logging.Logger;
+import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
+import org.jboss.resteasy.spi.HttpRequest;
+import org.jboss.resteasy.spi.HttpResponse;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.services.managers.TokenManager;
+import org.keycloak.services.messages.Messages;
+import org.keycloak.services.models.*;
+import org.keycloak.services.resources.flows.Flows;
+import org.keycloak.services.resources.flows.OAuthFlows;
+import org.keycloak.services.resources.flows.PageFlows;
+import org.keycloak.services.resources.flows.Urls;
+import org.keycloak.social.AuthCallback;
+import org.keycloak.social.AuthRequest;
+import org.keycloak.social.RequestDetails;
+import org.keycloak.social.RequestDetailsBuilder;
+import org.keycloak.social.SocialConstants;
+import org.keycloak.social.SocialProvider;
+import org.keycloak.social.SocialProviderConfig;
+import org.keycloak.social.SocialProviderException;
+import org.keycloak.social.SocialRequestManager;
+import org.keycloak.social.SocialUser;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -70,13 +90,20 @@ public class SocialResource {
     private HttpRequest request;
 
     @Context
+    private HttpResponse response;
+
+    @Context
+    ResourceContext resourceContext;
+
+    @Context
     protected KeycloakSession session;
 
-    protected SocialRequestManager socialRequestManager;
 
-    protected TokenManager tokenManager;
+    private SocialRequestManager socialRequestManager;
+
+    private TokenManager tokenManager;
 
-    protected AuthenticationManager authManager = new AuthenticationManager();
+    private AuthenticationManager authManager = new AuthenticationManager();
 
     public SocialResource(TokenManager tokenManager, SocialRequestManager socialRequestManager) {
         this.tokenManager = tokenManager;
@@ -102,10 +129,6 @@ public class SocialResource {
             return oauth.forwardToSecurityFailure("Realm not enabled.");
         }
 
-        if (!realm.isEnabled()) {
-            return oauth.forwardToSecurityFailure("Realm not enabled.");
-        }
-
         String clientId = requestData.getClientAttributes().get("clientId");
 
         UserModel client = realm.getUser(clientId);
@@ -131,21 +154,54 @@ public class SocialResource {
             return oauth.forwardToSecurityFailure("Failed to process social callback");
         }
 
-        // TODO Lookup user based on attribute for provider id - this is so a user can have a friendly username + link a
-        // user to
-        // multiple social logins
-        UserModel user = realm.getUser(provider.getId() + "." + socialUser.getId());
+        SocialLinkModel socialLink = new SocialLinkModel(provider.getId(), socialUser.getUsername());
+        UserModel user = realm.getUserBySocialLink(socialLink);
 
         if (user == null) {
             if (!realm.isRegistrationAllowed()) {
                 return oauth.forwardToSecurityFailure("Registration not allowed");
             }
 
-            user = realm.addUser(provider.getId() + "." + socialUser.getId());
-            user.setAttribute(provider.getId() + ".id", socialUser.getId());
+            // Automatically register user into realm with his social username (don't redirect to registration screen)
+            if (realm.isAutomaticRegistrationAfterSocialLogin()) {
+
+                if (realm.getUser(socialUser.getUsername()) != null) {
+                    // TODO: Username is already in realm. Show message and let user to bind accounts after he re-authenticate
+                    throw new IllegalStateException("Username " + socialUser.getUsername() +
+                            " already registered in the realm. TODO: bind accounts...");
+
+                    // TODO: Maybe we should search also by email and bind accounts if user with this email is
+                    // already registered. But actually Keycloak allows duplicate emails
+                } else {
+                    user = realm.addUser(socialUser.getUsername());
+                    user.setFirstName(socialUser.getFirstName());
+                    user.setLastName(socialUser.getLastName());
+                    user.setEmail(socialUser.getEmail());
+                }
 
-            for (RoleModel role : realm.getDefaultRoles()) {
-                realm.grantRole(user, role);
+                realm.addSocialLink(user, socialLink);
+
+                for (RoleModel role : realm.getDefaultRoles()) {
+                    realm.grantRole(user, role);
+                }
+            }  else {
+                // Redirect user to registration screen with prefilled data from social provider
+                MultivaluedMap<String, String> formData = fillRegistrationFormWithSocialData(socialUser);
+
+                RequestDetailsBuilder reqDetailsBuilder = RequestDetailsBuilder.createFromRequestDetails(requestData);
+                reqDetailsBuilder.putSocialAttribute(SocialConstants.ATTR_SOCIAL_LINK, socialLink);
+
+                String requestId = UUID.randomUUID().toString();
+                socialRequestManager.addRequest(requestId, reqDetailsBuilder.build());
+                boolean secureOnly = !realm.isSslNotRequired();
+                String cookiePath = Urls.socialBase(uriInfo.getBaseUri()).build().getPath();
+                logger.info("creating cookie for social registration - name: " + SocialConstants.SOCIAL_REGISTRATION_COOKIE
+                        + " path: " + cookiePath);
+                NewCookie newCookie = new NewCookie(SocialConstants.SOCIAL_REGISTRATION_COOKIE, requestId,
+                        cookiePath, null, "Added social cookie", NewCookie.DEFAULT_MAX_AGE, secureOnly);
+                response.addNewCookie(newCookie);
+
+                return Flows.forms(realm, request).setFormData(formData).setSocialRegistration(true).forwardToRegistration();
             }
         }
 
@@ -193,6 +249,66 @@ public class SocialResource {
         }
     }
 
+    @POST
+    @Path("{realm}/socialRegistration")
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response socialRegistration(@PathParam("realm") final String realmId,
+                                       final MultivaluedMap<String, String> formData) {
+        PageFlows pageFlows = Flows.pages(request);
+        Cookie cookie = headers.getCookies().get(SocialConstants.SOCIAL_REGISTRATION_COOKIE);
+        if (cookie == null) {
+            return pageFlows.forwardToSecurityFailure("Social registration cookie not found");
+        }
+
+        String requestId = cookie.getValue();
+        if (!socialRequestManager.isRequestId(requestId)) {
+            logger.error("Unknown requestId found in cookie. Maybe it's expired. requestId=" + requestId);
+            return pageFlows.forwardToSecurityFailure("Unknown requestId found in cookie. Maybe it's expired.");
+        }
+
+        RequestDetails requestData = socialRequestManager.getData(requestId);
+
+        RealmManager realmManager = new RealmManager(session);
+        RealmModel realm = realmManager.getRealm(realmId);
+        if (realm == null || !realm.isEnabled()) {
+            return pageFlows.forwardToSecurityFailure("Realm doesn't exists or is not enabled.");
+        }
+        TokenService tokenService = new TokenService(realm, tokenManager);
+        resourceContext.initResource(tokenService);
+
+        String clientId = requestData.getClientAttribute("clientId");
+        String scope = requestData.getClientAttribute("scope");
+        String state = requestData.getClientAttribute("state");
+        String redirectUri = requestData.getClientAttribute("redirectUri");
+        SocialLinkModel socialLink = (SocialLinkModel)requestData.getSocialAttribute(SocialConstants.ATTR_SOCIAL_LINK);
+
+        Response response1 = tokenService.processRegisterImpl(clientId, scope, state, redirectUri, formData, true);
+
+        // Some error occured during registration
+        if (response1 != null || request.wasForwarded()) {
+            logger.warn("Registration attempt wasn't successful. Request already forwarded or redirected.");
+            return response1;
+        }
+
+        String username = formData.getFirst("username");
+        UserModel user = realm.getUser(username);
+        if (user == null) {
+            // Normally shouldn't happen
+            throw new IllegalStateException("User " + username + " not found in the realm");
+        }
+        realm.addSocialLink(user, socialLink);
+
+        // Expire cookie and invalidate requestData
+        String cookiePath = Urls.socialBase(uriInfo.getBaseUri()).build().getPath();
+        NewCookie newCookie = new NewCookie(SocialConstants.SOCIAL_REGISTRATION_COOKIE, "", cookiePath, null,
+                "Expire social cookie", 0, false);
+        logger.info("Expiring social registration cookie: " + SocialConstants.SOCIAL_REGISTRATION_COOKIE + ", path: " + cookiePath);
+        response.addNewCookie(newCookie);
+        socialRequestManager.retrieveData(requestId);
+
+        return tokenService.processLogin(clientId, scope, state, redirectUri, formData);
+    }
+
     private RequestDetails getRequestDetails(Map<String, String[]> queryParams) {
         Iterator<SocialProvider> itr = ServiceRegistry.lookupProviders(SocialProvider.class);
 
@@ -231,4 +347,25 @@ public class SocialResource {
         return queryParams;
     }
 
+    protected MultivaluedMap<String, String> fillRegistrationFormWithSocialData(SocialUser socialUser) {
+        MultivaluedMap<String, String> formData = new MultivaluedMapImpl<String, String>();
+        formData.putSingle("username", socialUser.getUsername());
+
+        if (socialUser.getEmail() != null) {
+            formData.putSingle("email", socialUser.getEmail());
+        }
+
+        String fullName = null;
+        if (socialUser.getFirstName() == null) {
+            fullName = socialUser.getLastName();
+        } else if (socialUser.getLastName() == null) {
+            fullName = socialUser.getFirstName();
+        } else {
+            fullName = socialUser.getFirstName() + " " + socialUser.getLastName();
+        }
+
+        formData.putSingle("name", fullName);
+        return formData;
+    }
+
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java
index 133ebe4..00b37ad 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -11,18 +11,44 @@ import org.jboss.resteasy.spi.HttpResponse;
 import org.keycloak.representations.AccessTokenResponse;
 import org.keycloak.representations.SkeletonKeyToken;
 import org.keycloak.representations.idm.CredentialRepresentation;
-import org.keycloak.services.managers.*;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.services.managers.AccessCodeEntry;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.services.managers.ResourceAdminManager;
+import org.keycloak.services.managers.TokenManager;
 import org.keycloak.services.messages.Messages;
 import org.keycloak.services.models.*;
 import org.keycloak.services.resources.flows.Flows;
 import org.keycloak.services.resources.flows.OAuthFlows;
 import org.keycloak.services.validation.Validation;
-
-import javax.ws.rs.*;
-import javax.ws.rs.core.*;
+import org.picketlink.idm.credential.util.TimeBasedOTP;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.ForbiddenException;
+import javax.ws.rs.GET;
+import javax.ws.rs.NotAuthorizedException;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.NewCookie;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.SecurityContext;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
 import javax.ws.rs.ext.Providers;
+
 import java.security.PrivateKey;
-import java.util.*;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -38,25 +64,18 @@ public class TokenService {
 
     @Context
     protected Providers providers;
-
     @Context
     protected SecurityContext securityContext;
-
     @Context
     protected UriInfo uriInfo;
-
     @Context
     protected HttpHeaders headers;
-
     @Context
     protected HttpRequest request;
-
     @Context
     protected HttpResponse response;
-
     @Context
     protected KeycloakSession session;
-
     @Context
     protected KeycloakTransaction transaction;
 
@@ -215,6 +234,19 @@ public class TokenService {
     public Response processRegister(@QueryParam("client_id") final String clientId,
             @QueryParam("scope") final String scopeParam, @QueryParam("state") final String state,
             @QueryParam("redirect_uri") final String redirect, final MultivaluedMap<String, String> formData) {
+        Response registrationResponse = processRegisterImpl(clientId, scopeParam, state, redirect, formData, false);
+
+        // If request has been already forwarded (either due to security or validation error) then we won't continue with login
+        if (registrationResponse != null || request.wasForwarded()) {
+            logger.warn("Registration attempt wasn't successful. Request already forwarded or redirected.");
+            return registrationResponse;
+        } else {
+            return processLogin(clientId, scopeParam, state, redirect, formData);
+        }
+    }
+
+    public Response processRegisterImpl(String clientId, String scopeParam, String state, String redirect,
+                                        MultivaluedMap<String, String> formData, boolean isSocialRegistration) {
         OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
 
         if (!realm.isEnabled()) {
@@ -240,7 +272,8 @@ public class TokenService {
 
         String error = Validation.validateRegistrationForm(formData, requiredCredentialTypes);
         if (error != null) {
-            return Flows.forms(realm, request).setError(error).setFormData(formData).forwardToRegistration();
+            return Flows.forms(realm, request).setError(error).setFormData(formData)
+                    .setSocialRegistration(isSocialRegistration).forwardToRegistration();
         }
 
         String username = formData.getFirst("username");
@@ -248,7 +281,7 @@ public class TokenService {
         UserModel user = realm.getUser(username);
         if (user != null) {
             return Flows.forms(realm, request).setError(Messages.USERNAME_EXISTS).setFormData(formData)
-                    .forwardToRegistration();
+                    .setSocialRegistration(isSocialRegistration).forwardToRegistration();
         }
 
         user = realm.addUser(username);
@@ -290,7 +323,7 @@ public class TokenService {
             realm.grantRole(user, role);
         }
 
-        return processLogin(clientId, scopeParam, state, redirect, formData);
+        return null;
     }
 
     @Path("access/codes")
diff --git a/services/src/test/java/org/keycloak/test/AdapterTest.java b/services/src/test/java/org/keycloak/test/AdapterTest.java
index 2012264..aad9785 100755
--- a/services/src/test/java/org/keycloak/test/AdapterTest.java
+++ b/services/src/test/java/org/keycloak/test/AdapterTest.java
@@ -75,6 +75,7 @@ public class AdapterTest {
         realmModel.setPrivateKeyPem("0234234");
         realmModel.setPublicKeyPem("0234234");
         realmModel.setTokenLifespan(1000);
+        realmModel.setAutomaticRegistrationAfterSocialLogin(true);
         realmModel.addDefaultRole("foo");
 
         System.out.println(realmModel.getId());
@@ -86,6 +87,7 @@ public class AdapterTest {
         Assert.assertEquals(realmModel.getName(), "JUGGLER");
         Assert.assertEquals(realmModel.getPrivateKeyPem(), "0234234");
         Assert.assertEquals(realmModel.getPublicKeyPem(), "0234234");
+        Assert.assertEquals(realmModel.isAutomaticRegistrationAfterSocialLogin(), true);
         Assert.assertEquals(1, realmModel.getDefaultRoles().size());
         Assert.assertEquals("foo", realmModel.getDefaultRoles().get(0).getName());
     }
diff --git a/services/src/test/java/org/keycloak/test/ImportTest.java b/services/src/test/java/org/keycloak/test/ImportTest.java
index 1e67cd4..33dbebe 100755
--- a/services/src/test/java/org/keycloak/test/ImportTest.java
+++ b/services/src/test/java/org/keycloak/test/ImportTest.java
@@ -15,6 +15,7 @@ import org.keycloak.services.models.RealmModel;
 import org.keycloak.services.models.RequiredCredentialModel;
 import org.keycloak.services.models.ApplicationModel;
 import org.keycloak.services.models.RoleModel;
+import org.keycloak.services.models.SocialLinkModel;
 import org.keycloak.services.models.UserModel;
 import org.keycloak.services.resources.KeycloakApplication;
 import org.keycloak.services.resources.SaasService;
@@ -58,6 +59,7 @@ public class ImportTest {
         defaultRealm.setSslNotRequired(false);
         defaultRealm.setCookieLoginAllowed(true);
         defaultRealm.setRegistrationAllowed(true);
+        defaultRealm.setAutomaticRegistrationAfterSocialLogin(false);
         manager.generateRealmKeys(defaultRealm);
         defaultRealm.addRequiredCredential(CredentialRepresentation.PASSWORD);
         RoleModel role = defaultRealm.addRole(SaasService.REALM_CREATOR_ROLE);
@@ -68,6 +70,8 @@ public class ImportTest {
         RealmModel realm = manager.createRealm("demo", rep.getRealm());
         manager.importRealm(rep, realm);
         realm.addRealmAdmin(admin);
+
+        Assert.assertFalse(realm.isAutomaticRegistrationAfterSocialLogin());
         List<RequiredCredentialModel> creds = realm.getRequiredCredentials();
         Assert.assertEquals(1, creds.size());
         RequiredCredentialModel cred = creds.get(0);
@@ -82,6 +86,8 @@ public class ImportTest {
         Set<String> scopes = realm.getScope(user);
         System.out.println("Scopes size: " + scopes.size());
         Assert.assertTrue(scopes.contains("*"));
+        Assert.assertEquals(0, realm.getSocialLinks(user).size());
+
         List<ApplicationModel> resources = realm.getApplications();
         Assert.assertEquals(2, resources.size());
         List<RealmModel> realms = identitySession.getRealms(admin);
@@ -94,6 +100,28 @@ public class ImportTest {
         Assert.assertNotNull(oauthClient);
         Set<String> appScopes = application.getScope(oauthClient);
         Assert.assertTrue(appScopes.contains("user"));
+
+        // Test social linking
+        UserModel socialUser = realm.getUser("mySocialUser");
+        Set<SocialLinkModel> socialLinks = realm.getSocialLinks(socialUser);
+        Assert.assertEquals(3, socialLinks.size());
+        int facebookCount = 0;
+        int googleCount = 0;
+        for (SocialLinkModel socialLinkModel : socialLinks) {
+            if ("facebook".equals(socialLinkModel.getSocialProvider())) {
+                facebookCount++;
+            } else if ("google".equals(socialLinkModel.getSocialProvider())) {
+                googleCount++;
+                Assert.assertEquals(socialLinkModel.getSocialUsername(), "mySocialUser@gmail.com");
+            }
+        }
+        Assert.assertEquals(2, facebookCount);
+        Assert.assertEquals(1, googleCount);
+
+        UserModel foundSocialUser = realm.getUserBySocialLink(new SocialLinkModel("facebook", "fbuser1"));
+        Assert.assertEquals(foundSocialUser.getLoginName(), socialUser.getLoginName());
+        Assert.assertNull(realm.getUserBySocialLink(new SocialLinkModel("facebook", "not-existing")));
+
     }
 
     @Test
@@ -106,6 +134,7 @@ public class ImportTest {
         defaultRealm.setSslNotRequired(false);
         defaultRealm.setCookieLoginAllowed(true);
         defaultRealm.setRegistrationAllowed(true);
+        defaultRealm.setAutomaticRegistrationAfterSocialLogin(false);
         manager.generateRealmKeys(defaultRealm);
         defaultRealm.addRequiredCredential(CredentialRepresentation.PASSWORD);
         RoleModel role = defaultRealm.addRole(SaasService.REALM_CREATOR_ROLE);
@@ -117,6 +146,7 @@ public class ImportTest {
         manager.importRealm(rep, realm);
         realm.addRealmAdmin(admin);
 
+        Assert.assertTrue(realm.isAutomaticRegistrationAfterSocialLogin());
         verifyRequiredCredentials(realm.getRequiredCredentials(), "password");
         verifyRequiredCredentials(realm.getRequiredApplicationCredentials(), "totp");
         verifyRequiredCredentials(realm.getRequiredOAuthClientCredentials(), "cert");
diff --git a/services/src/test/resources/testrealm.json b/services/src/test/resources/testrealm.json
index b1e3e39..b2c4454 100755
--- a/services/src/test/resources/testrealm.json
+++ b/services/src/test/resources/testrealm.json
@@ -50,6 +50,10 @@
                     "value": "clientpassword"
                 }
             ]
+        },
+        {
+            "username": "mySocialUser",
+            "enabled": true
         }
     ],
     "roleMappings": [
@@ -64,6 +68,25 @@
             "roles": ["*"]
         }
     ],
+    "socialMappings": [
+        {
+            "username": "mySocialUser",
+            "socialLinks": [
+                {
+                    "socialProvider": "facebook",
+                    "socialUsername": "fbuser1"
+                },
+                {
+                    "socialProvider": "facebook",
+                    "socialUsername": "fbuser2"
+                },
+                {
+                    "socialProvider": "google",
+                    "socialUsername": "mySocialUser@gmail.com"
+                }
+            ]
+        }
+    ],
     "applications": [
         {
             "name": "Application",
diff --git a/services/src/test/resources/testrealm-demo.json b/services/src/test/resources/testrealm-demo.json
index 75007a3..ffb5f98 100755
--- a/services/src/test/resources/testrealm-demo.json
+++ b/services/src/test/resources/testrealm-demo.json
@@ -5,6 +5,7 @@
     "accessCodeLifespan": 10,
     "sslNotRequired": true,
     "cookieLoginAllowed": true,
+    "automaticRegistrationAfterSocialLogin": true,
     "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
     "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
     "requiredCredentials": [ "password" ],
diff --git a/social/core/src/main/java/org/keycloak/social/AuthCallback.java b/social/core/src/main/java/org/keycloak/social/AuthCallback.java
index 16fc089..8e21a21 100644
--- a/social/core/src/main/java/org/keycloak/social/AuthCallback.java
+++ b/social/core/src/main/java/org/keycloak/social/AuthCallback.java
@@ -28,16 +28,16 @@ import java.util.Map;
  */
 public class AuthCallback {
     
-    private Map<String, String> attributes;
+    private Map<String, Object> attributes;
 
     private Map<String, String[]> queryParams;
 
-    public AuthCallback(Map<String, String> attributes, Map<String, String[]> queryParams) {
+    public AuthCallback(Map<String, Object> attributes, Map<String, String[]> queryParams) {
         this.attributes = attributes;
         this.queryParams = queryParams;
     }
 
-    public String getAttribute(String name) {
+    public Object getAttribute(String name) {
         return attributes.get(name);
     }
 
diff --git a/social/core/src/main/java/org/keycloak/social/AuthRequest.java b/social/core/src/main/java/org/keycloak/social/AuthRequest.java
index a88d805..69731e9 100644
--- a/social/core/src/main/java/org/keycloak/social/AuthRequest.java
+++ b/social/core/src/main/java/org/keycloak/social/AuthRequest.java
@@ -33,9 +33,9 @@ public class AuthRequest {
 
     private URI authUri;
 
-    private Map<String, String> attributes;
+    private Map<String, Object> attributes;
 
-    AuthRequest(String id, URI authUri, Map<String, String> attributes) {
+    AuthRequest(String id, URI authUri, Map<String, Object> attributes) {
         this.id = id;
         this.authUri = authUri;
         this.attributes = attributes;
@@ -49,7 +49,7 @@ public class AuthRequest {
         return authUri;
     }
 
-    public Map<String, String> getAttributes() {
+    public Map<String, Object> getAttributes() {
         return attributes;
     }
 
diff --git a/social/core/src/main/java/org/keycloak/social/AuthRequestBuilder.java b/social/core/src/main/java/org/keycloak/social/AuthRequestBuilder.java
index 600783d..c5dd66a 100644
--- a/social/core/src/main/java/org/keycloak/social/AuthRequestBuilder.java
+++ b/social/core/src/main/java/org/keycloak/social/AuthRequestBuilder.java
@@ -33,7 +33,7 @@ public class AuthRequestBuilder {
 
     private UriBuilder b;
     
-    private Map<String, String> attributes;
+    private Map<String, Object> attributes;
 
     private String id;
 
@@ -44,7 +44,7 @@ public class AuthRequestBuilder {
         AuthRequestBuilder req = new AuthRequestBuilder();
         req.id = id;
         req.b = UriBuilder.fromUri(path);
-        req.attributes = new HashMap<String, String>();
+        req.attributes = new HashMap<String, Object>();
         return req;
     }
 
@@ -53,7 +53,7 @@ public class AuthRequestBuilder {
         return this;
     }
 
-    public AuthRequestBuilder setAttribute(String name, String value) {
+    public AuthRequestBuilder setAttribute(String name, Object value) {
         attributes.put(name, value);
         return this;
     }
diff --git a/social/core/src/main/java/org/keycloak/social/RequestDetails.java b/social/core/src/main/java/org/keycloak/social/RequestDetails.java
index 0476a64..f77f59b 100644
--- a/social/core/src/main/java/org/keycloak/social/RequestDetails.java
+++ b/social/core/src/main/java/org/keycloak/social/RequestDetails.java
@@ -32,9 +32,9 @@ public class RequestDetails {
 
     private Map<String, String> clientAttributes;
 
-    private Map<String, String> socialAttributes;
+    private Map<String, Object> socialAttributes;
 
-    RequestDetails(String providerId, Map<String, String> clientAttributes, Map<String, String> socialAttributes) {
+    RequestDetails(String providerId, Map<String, String> clientAttributes, Map<String, Object> socialAttributes) {
         this.providerId = providerId;
         this.clientAttributes = clientAttributes;
         this.socialAttributes = socialAttributes;
@@ -52,11 +52,11 @@ public class RequestDetails {
         return clientAttributes;
     }
 
-    public String getSocialAttribute(String name) {
+    public Object getSocialAttribute(String name) {
         return socialAttributes.get(name);
     }
 
-    public Map<String, String> getSocialAttributes() {
+    public Map<String, Object> getSocialAttributes() {
         return socialAttributes;
     }
 
diff --git a/social/core/src/main/java/org/keycloak/social/RequestDetailsBuilder.java b/social/core/src/main/java/org/keycloak/social/RequestDetailsBuilder.java
index a8d8286..aa86fbe 100644
--- a/social/core/src/main/java/org/keycloak/social/RequestDetailsBuilder.java
+++ b/social/core/src/main/java/org/keycloak/social/RequestDetailsBuilder.java
@@ -33,7 +33,7 @@ public class RequestDetailsBuilder {
 
     private Map<String, String> clientAttributes;
 
-    private Map<String, String> socialAttributes;
+    private Map<String, Object> socialAttributes;
 
     private RequestDetailsBuilder() {
     }
@@ -42,7 +42,17 @@ public class RequestDetailsBuilder {
         RequestDetailsBuilder req = new RequestDetailsBuilder();
         req.providerId = providerId;
         req.clientAttributes = new HashMap<String, String>();
-        req.socialAttributes = new HashMap<String, String>();
+        req.socialAttributes = new HashMap<String, Object>();
+        return req;
+    }
+
+    public static RequestDetailsBuilder createFromRequestDetails(RequestDetails from) {
+        RequestDetailsBuilder req = new RequestDetailsBuilder();
+        req.providerId = from.getProviderId();
+        req.clientAttributes = new HashMap<String, String>();
+        req.clientAttributes.putAll(from.getClientAttributes());
+        req.socialAttributes = new HashMap<String, Object>();
+        req.socialAttributes.putAll(from.getSocialAttributes());
         return req;
     }
 
@@ -56,12 +66,12 @@ public class RequestDetailsBuilder {
         return this;
     }
 
-    public RequestDetailsBuilder putSocialAttribute(String name, String value) {
+    public RequestDetailsBuilder putSocialAttribute(String name, Object value) {
         socialAttributes.put(name, value);
         return this;
     }
 
-    public RequestDetailsBuilder putSocialAttributes(Map<String, String> attributes) {
+    public RequestDetailsBuilder putSocialAttributes(Map<String, Object> attributes) {
         socialAttributes.putAll(attributes);
         return this;
     }
diff --git a/social/core/src/main/java/org/keycloak/social/SocialConstants.java b/social/core/src/main/java/org/keycloak/social/SocialConstants.java
new file mode 100644
index 0000000..f1d5cd7
--- /dev/null
+++ b/social/core/src/main/java/org/keycloak/social/SocialConstants.java
@@ -0,0 +1,11 @@
+package org.keycloak.social;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class SocialConstants {
+
+    public static final String ATTR_SOCIAL_LINK = "ATTR_SOCIAL_LINK";
+
+    public static final String SOCIAL_REGISTRATION_COOKIE = "SOCIAL_REGISTRATION_COOKIE";
+}
diff --git a/social/core/src/main/java/org/keycloak/social/SocialRequestManager.java b/social/core/src/main/java/org/keycloak/social/SocialRequestManager.java
index 2993a9a..735ea6b 100644
--- a/social/core/src/main/java/org/keycloak/social/SocialRequestManager.java
+++ b/social/core/src/main/java/org/keycloak/social/SocialRequestManager.java
@@ -57,6 +57,11 @@ public class SocialRequestManager {
 
         return details;
     }
+
+    // Just obtain data without expiring it
+    public synchronized RequestDetails getData(String requestId) {
+        return map.get(requestId);
+    }
     
     private void pruneExpired() {
         long currentTime = System.currentTimeMillis();
diff --git a/social/core/src/main/java/org/keycloak/social/SocialUser.java b/social/core/src/main/java/org/keycloak/social/SocialUser.java
index fc0b7f2..f9485d6 100644
--- a/social/core/src/main/java/org/keycloak/social/SocialUser.java
+++ b/social/core/src/main/java/org/keycloak/social/SocialUser.java
@@ -3,6 +3,7 @@ package org.keycloak.social;
 public class SocialUser {
     
     private String id;
+    private String username;
     private String firstName;
     private String lastName;
     private String email;
@@ -19,6 +20,14 @@ public class SocialUser {
         this.id = id;
     }
 
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
     public String getFirstName() {
         return firstName;
     }
diff --git a/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookProvider.java b/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookProvider.java
index dbe4254..94410f1 100644
--- a/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookProvider.java
+++ b/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookProvider.java
@@ -82,6 +82,13 @@ public class FacebookProvider implements SocialProvider {
             FacebookUser facebookUser = loadUser(accessToken, client);
 
             SocialUser socialUser = new SocialUser(facebookUser.getId());
+            socialUser.setUsername(facebookUser.getUsername());
+
+            // This could happen with Facebook testing users
+            if (facebookUser.getUsername() == null || facebookUser.getUsername().length() == 0) {
+                socialUser.setUsername(facebookUser.getId());
+            }
+
             socialUser.setEmail(facebookUser.getEmail());
             socialUser.setLastName(facebookUser.getLastName());
             socialUser.setFirstName(facebookUser.getFirstName());
diff --git a/social/google/src/main/java/org/keycloak/social/google/GoogleProvider.java b/social/google/src/main/java/org/keycloak/social/google/GoogleProvider.java
index 9054c45..3a0febb 100644
--- a/social/google/src/main/java/org/keycloak/social/google/GoogleProvider.java
+++ b/social/google/src/main/java/org/keycloak/social/google/GoogleProvider.java
@@ -106,6 +106,10 @@ public class GoogleProvider implements SocialProvider {
             Userinfo userInfo = oauth2.userinfo().get().execute();
 
             SocialUser user = new SocialUser(userInfo.getId());
+
+            // Use email as username for Google
+            user.setUsername(userInfo.getEmail());
+
             user.setFirstName(userInfo.getGivenName());
             user.setLastName(userInfo.getFamilyName());
             user.setEmail(userInfo.getEmail());
diff --git a/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterProvider.java b/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterProvider.java
index d014e81..54c4cc2 100644
--- a/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterProvider.java
+++ b/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterProvider.java
@@ -71,13 +71,24 @@ public class TwitterProvider implements SocialProvider {
             twitter.setOAuthConsumer(config.getKey(), config.getSecret());
 
             String verifier = callback.getQueryParam("oauth_verifier");
-            RequestToken requestToken = new RequestToken(callback.getAttribute("token"), callback.getAttribute("tokenSecret"));
+            RequestToken requestToken = new RequestToken((String)callback.getAttribute("token"), (String)callback.getAttribute("tokenSecret"));
 
             twitter.getOAuthAccessToken(requestToken, verifier);
             twitter4j.User twitterUser = twitter.verifyCredentials();
 
             SocialUser user = new SocialUser(Long.toString(twitterUser.getId()));
-            user.setFirstName(twitterUser.getName());
+
+            // Use screenName as username for Twitter
+            user.setUsername(twitterUser.getScreenName());
+
+            String twitterName = twitterUser.getName();
+            int spaceIndex = twitterName.lastIndexOf(' ');
+            if (spaceIndex != -1) {
+                user.setFirstName(twitterName.substring(0, spaceIndex));
+                user.setLastName(twitterName.substring(spaceIndex + 1));
+            } else {
+                user.setFirstName(twitterName);
+            }
 
             return user;
         } catch (Exception e) {