keycloak-memoizeit

KEYCLOAK-27 Basic social flow with automatic registration

8/19/2013 1:14:40 PM

Details

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..f69d0bd 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,7 @@
     "sslNotRequired": true,
     "cookieLoginAllowed": true,
     "registrationAllowed": 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/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/resources/SocialResource.java b/services/src/main/java/org/keycloak/services/resources/SocialResource.java
index 91a36ca..c5596f4 100644
--- a/services/src/main/java/org/keycloak/services/resources/SocialResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/SocialResource.java
@@ -46,6 +46,7 @@ import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.managers.TokenManager;
 import org.keycloak.services.models.RealmModel;
 import org.keycloak.services.models.RoleModel;
+import org.keycloak.services.models.SocialLinkModel;
 import org.keycloak.services.models.UserModel;
 import org.keycloak.services.resources.flows.Flows;
 import org.keycloak.services.resources.flows.OAuthFlows;
@@ -134,21 +135,35 @@ 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()) {
 
-                    for (RoleModel role : realm.getDefaultRoles()) {
-                        realm.grantRole(user, role);
+                        if (realm.getUser(socialUser.getUsername()) != null) {
+                            // TODO: Username is already in realm. Show message and let user to bind accounts
+                            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());
+                        }
+
+                        realm.addSocialLink(user, socialLink);
+
+                        for (RoleModel role : realm.getDefaultRoles()) {
+                            realm.grantRole(user, role);
+                        }
+                    }  else {
+                        // TODO: redirect to registration screen with pre-filled info
                     }
                 }
 
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/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..49b6e6e 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
@@ -77,7 +77,18 @@ public class TwitterProvider implements SocialProvider {
             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) {