keycloak-aplcache

Merge pull request #1034 from velias/KEYCLOAK-1074 #1074

3/12/2015 4:36:05 PM

Changes

Details

diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml
index f1a35c9..a723490 100755
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml
@@ -93,6 +93,7 @@
 
         <addColumn tableName="REALM">
             <column name="LOGIN_LIFESPAN" type="INT"/>
+            <column name="REGISTRATION_EMAIL_AS_USERNAME" type="BOOLEAN" defaultValueBoolean="false"/>
         </addColumn>
     </changeSet>
 </databaseChangeLog>
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 dce6df6..023172f 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -24,6 +24,7 @@ public class RealmRepresentation {
     protected String sslRequired;
     protected Boolean passwordCredentialGrantAllowed;
     protected Boolean registrationAllowed;
+    protected Boolean registrationEmailAsUsername;
     protected Boolean rememberMe;
     protected Boolean verifyEmail;
     protected Boolean resetPasswordAllowed;
@@ -264,6 +265,14 @@ public class RealmRepresentation {
         this.registrationAllowed = registrationAllowed;
     }
 
+    public Boolean isRegistrationEmailAsUsername() {
+        return registrationEmailAsUsername;
+    }
+
+    public void setRegistrationEmailAsUsername(Boolean registrationEmailAsUsername) {
+        this.registrationEmailAsUsername = registrationEmailAsUsername;
+    }
+
     public Boolean isRememberMe() {
         return rememberMe;
     }
diff --git a/events/api/src/main/java/org/keycloak/events/Errors.java b/events/api/src/main/java/org/keycloak/events/Errors.java
index 7a4404d..282b5e4 100755
--- a/events/api/src/main/java/org/keycloak/events/Errors.java
+++ b/events/api/src/main/java/org/keycloak/events/Errors.java
@@ -41,6 +41,7 @@ public interface Errors {
     String NOT_ALLOWED = "not_allowed";
 
     String FEDERATED_IDENTITY_EMAIL_EXISTS = "federated_identity_email_exists";
+    String FEDERATED_IDENTITY_REGISTRATION_EMAIL_MISSING = "federated_identity_registration_email_missing";
     String FEDERATED_IDENTITY_USERNAME_EXISTS = "federated_identity_username_exists";
     String SSL_REQUIRED = "ssl_required";
 
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-login-settings.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-login-settings.html
index bd1bab9..e491c21 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-login-settings.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-login-settings.html
@@ -12,7 +12,14 @@
                         <div class="col-sm-4">
                             <input ng-model="realm.registrationAllowed" name="registrationAllowed" id="registrationAllowed" onoffswitch />
                         </div>
-                        <span tooltip-placement="right" tooltip="Enable/disable the registration page.  A link for registration will show on login page too." class="fa fa-info-circle"></span>
+                        <span tooltip-placement="right" tooltip="Enable/disable the registration page. A link for registration will show on login page too." class="fa fa-info-circle"></span>
+                    </div>
+                    <div class="form-group" ng-show="registrationAllowed">
+                        <label for="registrationEmailAsUsername" class="col-sm-2 control-label">Email as username</label>
+                        <div class="col-sm-4">
+                            <input ng-model="realm.registrationEmailAsUsername" name="registrationEmailAsUsername" id="registrationEmailAsUsername" onoffswitch />
+                        </div>
+                        <span tooltip-placement="right" tooltip="If enabled then username field is hidden from registration form and email is used as username for new user." class="fa fa-info-circle"></span>
                     </div>
                     <div class="form-group">
                         <label for="resetPasswordAllowed" class="col-sm-2 control-label">Forget password</label>
diff --git a/forms/common-themes/src/main/resources/theme/login/base/login.ftl b/forms/common-themes/src/main/resources/theme/login/base/login.ftl
index a1a3b23..e46cf95 100755
--- a/forms/common-themes/src/main/resources/theme/login/base/login.ftl
+++ b/forms/common-themes/src/main/resources/theme/login/base/login.ftl
@@ -17,7 +17,7 @@
             <form id="kc-form-login" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
                 <div class="${properties.kcFormGroupClass!}">
                     <div class="${properties.kcLabelWrapperClass!}">
-                        <label for="username" class="${properties.kcLabelClass!}">${rb.usernameOrEmail}</label>
+                        <label for="username" class="${properties.kcLabelClass!}"><#if !realm.registrationEmailAsUsername>${rb.usernameOrEmail}<#else>${rb.email}</#if></label>
                     </div>
 
                     <div class="${properties.kcInputWrapperClass!}">
diff --git a/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties b/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties
index 2f43473..0a59a69 100755
--- a/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties
+++ b/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties
@@ -62,6 +62,7 @@ emailExists=Email already exists
 
 federatedIdentityEmailExists=User with email already exists. Please login to account management to link the account.
 federatedIdentityUsernameExists=User with username already exists. Please login to account management to link the account.
+federatedIdentityRegistrationEmailMissing=Email is not provided. Use another provider to create account please.  
 
 loginTitle=Log in to
 loginOauthTitle=Temporary access.
diff --git a/forms/common-themes/src/main/resources/theme/login/base/register.ftl b/forms/common-themes/src/main/resources/theme/login/base/register.ftl
index b827865..492fbab 100755
--- a/forms/common-themes/src/main/resources/theme/login/base/register.ftl
+++ b/forms/common-themes/src/main/resources/theme/login/base/register.ftl
@@ -6,6 +6,7 @@
         ${rb.registerWith} <strong>${realm.name}</strong>
     <#elseif section = "form">
         <form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.registrationAction}" method="post">
+          <#if !realm.registrationEmailAsUsername>
             <div class="${properties.kcFormGroupClass!}">
                 <div class="${properties.kcLabelWrapperClass!}">
                     <label for="username" class="${properties.kcLabelClass!}">${rb.username}</label>
@@ -14,7 +15,7 @@
                     <input type="text" id="username" class="${properties.kcInputClass!}" name="username" value="${(register.formData.username!'')?html}" />
                 </div>
             </div>
-
+          </#if>
             <div class="${properties.kcFormGroupClass!}">
                 <div class="${properties.kcLabelWrapperClass!}">
                     <label for="firstName" class="${properties.kcLabelClass!}">${rb.firstName}</label>
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/RealmBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/RealmBean.java
index f751cf9..e4ac27f 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/RealmBean.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/RealmBean.java
@@ -48,6 +48,10 @@ public class RealmBean {
         return realm.isRegistrationAllowed();
     }
 
+    public boolean isRegistrationEmailAsUsername() {
+        return realm.isRegistrationEmailAsUsername();
+    }
+
     public boolean isResetPasswordAllowed() {
         return realm.isResetPasswordAllowed();
     }
diff --git a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
index 17792e5..38baa89 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
@@ -14,6 +14,7 @@ public class RealmEntity extends AbstractIdentifiableEntity {
     private boolean enabled;
     private String sslRequired;
     private boolean registrationAllowed;
+    protected boolean registrationEmailAsUsername;
     private boolean rememberMe;
     private boolean verifyEmail;
     private boolean passwordCredentialGrantAllowed;
@@ -104,6 +105,14 @@ public class RealmEntity extends AbstractIdentifiableEntity {
         this.registrationAllowed = registrationAllowed;
     }
 
+    public boolean isRegistrationEmailAsUsername() {
+        return registrationEmailAsUsername;
+    }
+
+    public void setRegistrationEmailAsUsername(boolean registrationEmailAsUsername) {
+        this.registrationEmailAsUsername = registrationEmailAsUsername;
+    }
+
     public boolean isRememberMe() {
         return rememberMe;
     }
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index 4212e3b..ab260a1 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -47,6 +47,10 @@ public interface RealmModel extends RoleContainerModel {
 
     void setRegistrationAllowed(boolean registrationAllowed);
 
+    public boolean isRegistrationEmailAsUsername();
+
+    public void setRegistrationEmailAsUsername(boolean registrationEmailAsUsername);
+
     boolean isPasswordCredentialGrantAllowed();
 
     void setPasswordCredentialGrantAllowed(boolean passwordCredentialGrantAllowed);
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index d0963df..686eede 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -99,6 +99,7 @@ public class ModelToRepresentation {
         rep.setCertificate(realm.getCertificatePem());
         rep.setPasswordCredentialGrantAllowed(realm.isPasswordCredentialGrantAllowed());
         rep.setRegistrationAllowed(realm.isRegistrationAllowed());
+        rep.setRegistrationEmailAsUsername(realm.isRegistrationEmailAsUsername());
         rep.setRememberMe(realm.isRememberMe());
         rep.setBruteForceProtected(realm.isBruteForceProtected());
         rep.setMaxFailureWaitSeconds(realm.getMaxFailureWaitSeconds());
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 90b00dd..ce87ada 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -85,6 +85,8 @@ public class RepresentationToModel {
         if (rep.getSslRequired() != null) newRealm.setSslRequired(SslRequired.valueOf(rep.getSslRequired().toUpperCase()));
         if (rep.isPasswordCredentialGrantAllowed() != null) newRealm.setPasswordCredentialGrantAllowed(rep.isPasswordCredentialGrantAllowed());
         if (rep.isRegistrationAllowed() != null) newRealm.setRegistrationAllowed(rep.isRegistrationAllowed());
+        if (rep.isRegistrationEmailAsUsername() != null)
+            newRealm.setRegistrationEmailAsUsername(rep.isRegistrationEmailAsUsername());
         if (rep.isRememberMe() != null) newRealm.setRememberMe(rep.isRememberMe());
         if (rep.isVerifyEmail() != null) newRealm.setVerifyEmail(rep.isVerifyEmail());
         if (rep.isResetPasswordAllowed() != null) newRealm.setResetPasswordAllowed(rep.isResetPasswordAllowed());
@@ -257,6 +259,7 @@ public class RepresentationToModel {
         if (rep.getFailureFactor() != null) realm.setFailureFactor(rep.getFailureFactor());
         if (rep.isPasswordCredentialGrantAllowed() != null) realm.setPasswordCredentialGrantAllowed(rep.isPasswordCredentialGrantAllowed());
         if (rep.isRegistrationAllowed() != null) realm.setRegistrationAllowed(rep.isRegistrationAllowed());
+        if (rep.isRegistrationEmailAsUsername() != null) realm.setRegistrationEmailAsUsername(rep.isRegistrationEmailAsUsername());
         if (rep.isRememberMe() != null) realm.setRememberMe(rep.isRememberMe());
         if (rep.isVerifyEmail() != null) realm.setVerifyEmail(rep.isVerifyEmail());
         if (rep.isResetPasswordAllowed() != null) realm.setResetPasswordAllowed(rep.isResetPasswordAllowed());
diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
index c2cfab8..5183b74 100755
--- a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
+++ b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
@@ -153,6 +153,16 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public boolean isRegistrationEmailAsUsername() {
+        return realm.isRegistrationEmailAsUsername();
+    }
+
+    @Override
+    public void setRegistrationEmailAsUsername(boolean registrationEmailAsUsername) {
+        realm.setRegistrationEmailAsUsername(registrationEmailAsUsername);
+    }
+
+    @Override
     public boolean isRememberMe() {
         return realm.isRememberMe();
     }
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
index 8089e4e..dc06240 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
@@ -33,6 +33,7 @@ public class CachedRealm {
     private boolean enabled;
     private SslRequired sslRequired;
     private boolean registrationAllowed;
+    private boolean registrationEmailAsUsername;
     private boolean rememberMe;
     private boolean verifyEmail;
     private boolean passwordCredentialGrantAllowed;
@@ -92,6 +93,7 @@ public class CachedRealm {
         enabled = model.isEnabled();
         sslRequired = model.getSslRequired();
         registrationAllowed = model.isRegistrationAllowed();
+        registrationEmailAsUsername = model.isRegistrationEmailAsUsername();
         rememberMe = model.isRememberMe();
         verifyEmail = model.isVerifyEmail();
         passwordCredentialGrantAllowed = model.isPasswordCredentialGrantAllowed();
@@ -205,6 +207,10 @@ public class CachedRealm {
         return registrationAllowed;
     }
 
+    public boolean isRegistrationEmailAsUsername() {
+        return registrationEmailAsUsername;
+    }
+
     public boolean isPasswordCredentialGrantAllowed() {
         return passwordCredentialGrantAllowed;
     }
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
index 57013cf..a58774d 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
@@ -109,6 +109,18 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public boolean isRegistrationEmailAsUsername() {
+        if (updated != null) return updated.isRegistrationEmailAsUsername();
+        return cached.isRegistrationEmailAsUsername();
+    }
+
+    @Override
+    public void setRegistrationEmailAsUsername(boolean registrationEmailAsUsername) {
+        getDelegateForUpdate();
+        updated.setRegistrationEmailAsUsername(registrationEmailAsUsername);
+    }
+    
+    @Override
     public boolean isPasswordCredentialGrantAllowed() {
         if (updated != null) return updated.isPasswordCredentialGrantAllowed();
         return cached.isPasswordCredentialGrantAllowed();
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index 9a4358b..1ba7a55 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -47,6 +47,8 @@ public class RealmEntity {
     protected String sslRequired;
     @Column(name="REGISTRATION_ALLOWED")
     protected boolean registrationAllowed;
+    @Column(name = "REGISTRATION_EMAIL_AS_USERNAME")
+    protected boolean registrationEmailAsUsername;
     @Column(name="PASSWORD_CRED_GRANT_ALLOWED")
     protected boolean passwordCredentialGrantAllowed;
     @Column(name="VERIFY_EMAIL")
@@ -183,6 +185,14 @@ public class RealmEntity {
         this.registrationAllowed = registrationAllowed;
     }
 
+    public boolean isRegistrationEmailAsUsername() {
+        return registrationEmailAsUsername;
+    }
+
+    public void setRegistrationEmailAsUsername(boolean registrationEmailAsUsername) {
+        this.registrationEmailAsUsername = registrationEmailAsUsername;
+    }
+
     public boolean isRememberMe() {
         return rememberMe;
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 9c0e751..a17dd17 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -124,6 +124,17 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public boolean isRegistrationEmailAsUsername() {
+        return realm.isRegistrationEmailAsUsername();
+    }
+
+    @Override
+    public void setRegistrationEmailAsUsername(boolean registrationEmailAsUsername) {
+        realm.setRegistrationEmailAsUsername(registrationEmailAsUsername);
+        em.flush();
+    }
+
+    @Override
     public boolean isRememberMe() {
         return realm.isRememberMe();
     }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index 897ac6f..9bee5a5 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -123,6 +123,15 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
         updateRealm();
     }
 
+    public boolean isRegistrationEmailAsUsername() {
+        return realm.isRegistrationEmailAsUsername();
+    }
+
+    public void setRegistrationEmailAsUsername(boolean registrationEmailAsUsername) {
+        realm.setRegistrationEmailAsUsername(registrationEmailAsUsername);
+        updateRealm();
+    }
+
     @Override
     public boolean isRememberMe() {
         return realm.isRememberMe();
diff --git a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
index 655b148..252a081 100755
--- a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
+++ b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
@@ -56,6 +56,7 @@ public class ApplianceBootstrap {
         realm.setAccessCodeLifespanUserAction(300);
         realm.setSslRequired(SslRequired.EXTERNAL);
         realm.setRegistrationAllowed(false);
+        realm.setRegistrationEmailAsUsername(false);
         KeycloakModelUtils.generateRealmKeys(realm);
 
         UserModel adminUser = session.users().addUser(realm, "admin");
diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
index d26f695..c95b618 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -538,7 +538,20 @@ public class IdentityBrokerService {
             throw new IdentityBrokerException("federatedIdentityEmailExists");
         }
 
-        existingUser = this.session.users().getUserByUsername(updatedIdentity.getUsername(), this.realmModel);
+        String username = updatedIdentity.getUsername();
+        if (this.realmModel.isRegistrationEmailAsUsername()) {
+            username = updatedIdentity.getEmail();
+            if (username == null || username.trim().length() == 0) {
+                fireErrorEvent(Errors.FEDERATED_IDENTITY_REGISTRATION_EMAIL_MISSING);
+                throw new IdentityBrokerException("federatedIdentityRegistrationEmailMissing");
+                // TODO KEYCLOAK-1053 (ask user to enter email address) should be implemented instead of plain exception as better solution for this case
+            }
+            username = username.trim();
+        } else if (username != null) {
+            username = username.trim();
+        }
+
+        existingUser = this.session.users().getUserByUsername(username, this.realmModel);
 
         if (existingUser != null) {
             fireErrorEvent(Errors.FEDERATED_IDENTITY_USERNAME_EXISTS);
@@ -549,7 +562,7 @@ public class IdentityBrokerService {
             LOGGER.debugf("Creating account from identity [%s].", federatedIdentityModel);
         }
 
-        UserModel federatedUser = this.session.users().addUser(this.realmModel, updatedIdentity.getUsername());
+        UserModel federatedUser = this.session.users().addUser(this.realmModel, username);
 
         if (isDebugEnabled()) {
             LOGGER.debugf("Account [%s] created.", federatedUser);
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index f62bd5c..0198644 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -430,6 +430,10 @@ public class LoginActionsService {
 
         String username = formData.getFirst("username");
         String email = formData.getFirst("email");
+        if (realm.isRegistrationEmailAsUsername()) {
+            username = email;
+            formData.putSingle(AuthenticationManager.FORM_USERNAME, username);
+        }
         ClientSessionModel clientSession = clientCode.getClientSession();
         event.client(clientSession.getClient())
                 .detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
@@ -460,7 +464,7 @@ public class LoginActionsService {
         }
 
         // Validate here, so user is not created if password doesn't validate to passwordPolicy of current realm
-        String error = Validation.validateRegistrationForm(formData, requiredCredentialTypes);
+        String error = Validation.validateRegistrationForm(realm, formData, requiredCredentialTypes);
         if (error == null) {
             error = Validation.validatePassword(formData, realm.getPasswordPolicy());
         }
diff --git a/services/src/main/java/org/keycloak/services/validation/Validation.java b/services/src/main/java/org/keycloak/services/validation/Validation.java
index a5a0f3f..254cee1 100755
--- a/services/src/main/java/org/keycloak/services/validation/Validation.java
+++ b/services/src/main/java/org/keycloak/services/validation/Validation.java
@@ -1,6 +1,7 @@
 package org.keycloak.services.validation;
 
 import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.RealmModel;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.services.messages.Messages;
 
@@ -13,7 +14,7 @@ public class Validation {
     // Actually allow same emails like angular. See ValidationTest.testEmailValidation()
     private static final Pattern EMAIL_PATTERN = Pattern.compile("[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*");
 
-    public static String validateRegistrationForm(MultivaluedMap<String, String> formData, List<String> requiredCredentialTypes) {
+    public static String validateRegistrationForm(RealmModel realm, MultivaluedMap<String, String> formData, List<String> requiredCredentialTypes) {
         if (isEmpty(formData.getFirst("firstName"))) {
             return Messages.MISSING_FIRST_NAME;
         }
@@ -30,7 +31,7 @@ public class Validation {
             return Messages.INVALID_EMAIL;
         }
 
-        if (isEmpty(formData.getFirst("username"))) {
+        if (!realm.isRegistrationEmailAsUsername() && isEmpty(formData.getFirst("username"))) {
             return Messages.MISSING_USERNAME;
         }
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java
index 100f9c1..302dd8d 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java
@@ -233,6 +233,7 @@ public class AdminAPITest {
         if (rep.getFailureFactor() != null) Assert.assertEquals(rep.getFailureFactor(), storedRealm.getFailureFactor());
         if (rep.isPasswordCredentialGrantAllowed() != null) Assert.assertEquals(rep.isPasswordCredentialGrantAllowed(), storedRealm.isPasswordCredentialGrantAllowed());
         if (rep.isRegistrationAllowed() != null) Assert.assertEquals(rep.isRegistrationAllowed(), storedRealm.isRegistrationAllowed());
+        if (rep.isRegistrationEmailAsUsername() != null) Assert.assertEquals(rep.isRegistrationEmailAsUsername(), storedRealm.isRegistrationEmailAsUsername());
         if (rep.isRememberMe() != null) Assert.assertEquals(rep.isRememberMe(), storedRealm.isRememberMe());
         if (rep.isVerifyEmail() != null) Assert.assertEquals(rep.isVerifyEmail(), storedRealm.isVerifyEmail());
         if (rep.isResetPasswordAllowed() != null) Assert.assertEquals(rep.isResetPasswordAllowed(), storedRealm.isResetPasswordAllowed());
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
index 5dd37bc..564cb85 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
@@ -60,10 +60,13 @@ public class RealmTest extends AbstractClientTest {
 
     @Test
     public void updateRealm() {
+        // first change
         RealmRepresentation rep = realm.toRepresentation();
         rep.setSsoSessionIdleTimeout(123);
         rep.setSsoSessionMaxLifespan(12);
         rep.setAccessCodeLifespanLogin(1234);
+        rep.setRegistrationAllowed(true);
+        rep.setRegistrationEmailAsUsername(true);
 
         realm.update(rep);
 
@@ -72,6 +75,18 @@ public class RealmTest extends AbstractClientTest {
         assertEquals(123, rep.getSsoSessionIdleTimeout().intValue());
         assertEquals(12, rep.getSsoSessionMaxLifespan().intValue());
         assertEquals(1234, rep.getAccessCodeLifespanLogin().intValue());
+        assertEquals(Boolean.TRUE, rep.isRegistrationAllowed());
+        assertEquals(Boolean.TRUE, rep.isRegistrationEmailAsUsername());
+
+        // second change
+        rep.setRegistrationAllowed(false);
+        rep.setRegistrationEmailAsUsername(false);
+
+        realm.update(rep);
+
+        rep = realm.toRepresentation();
+        assertEquals(Boolean.FALSE, rep.isRegistrationAllowed());
+        assertEquals(Boolean.FALSE, rep.isRegistrationEmailAsUsername());
     }
 
     @Test
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
index dd034c1..b1ae4cb 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
@@ -59,6 +59,7 @@ import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.UriBuilder;
+
 import java.io.IOException;
 import java.net.URI;
 import java.util.List;
@@ -68,6 +69,7 @@ import static com.thoughtworks.selenium.SeleneseTestBase.fail;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 /**
@@ -124,7 +126,7 @@ public abstract class AbstractIdentityProviderTest {
     public void testSuccessfulAuthentication() {
         IdentityProviderModel identityProviderModel = getIdentityProviderModel();
 
-        assertSuccessfulAuthentication(identityProviderModel);
+        assertSuccessfulAuthentication(identityProviderModel, "test-user");
     }
 
     @Test
@@ -132,7 +134,77 @@ public abstract class AbstractIdentityProviderTest {
         IdentityProviderModel identityProviderModel = getIdentityProviderModel();
         identityProviderModel.setUpdateProfileFirstLogin(false);
 
-        assertSuccessfulAuthentication(identityProviderModel);
+        assertSuccessfulAuthentication(identityProviderModel, "test-user");
+    }
+
+    @Test
+    public void testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername() {
+
+        getRealm().setRegistrationEmailAsUsername(true);
+        brokerServerRule.stopSession(this.session, true);
+        this.session = brokerServerRule.startSession();
+
+        try {
+            IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+            identityProviderModel.setUpdateProfileFirstLogin(false);
+
+            authenticateWithIdentityProvider(identityProviderModel, "test-user");
+
+            // authenticated and redirected to app
+            assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
+
+            // check correct user is created with email as username and bound to correct federated identity
+            RealmModel realm = getRealm();
+
+            UserModel federatedUser = session.users().getUserByUsername("test-user@localhost", realm);
+
+            assertNotNull(federatedUser);
+
+            assertEquals("test-user@localhost", federatedUser.getUsername());
+
+            doAssertFederatedUser(federatedUser, identityProviderModel);
+
+            Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
+
+            assertEquals(1, federatedIdentities.size());
+
+            FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next();
+
+            assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider());
+
+            driver.navigate().to("http://localhost:8081/test-app/logout");
+            driver.navigate().to("http://localhost:8081/test-app");
+
+            assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login"));
+
+        } finally {
+            getRealm().setRegistrationEmailAsUsername(false);
+        }
+    }
+
+    @Test
+    public void testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername_emailNotProvided() {
+
+        getRealm().setRegistrationEmailAsUsername(true);
+        brokerServerRule.stopSession(this.session, true);
+        this.session = brokerServerRule.startSession();
+
+        try {
+            IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+            identityProviderModel.setUpdateProfileFirstLogin(false);
+
+            authenticateWithIdentityProvider(identityProviderModel, "test-user-noemail");
+
+            RealmModel realm = getRealm();
+            UserModel federatedUser = session.users().getUserByUsername("test-user-noemail", realm);
+            assertNull(federatedUser);
+
+            // assert page is shown with correct error message
+            assertEquals("Email is not provided. Use another provider to create account please.", this.driver.findElement(By.className("kc-feedback-text")).getText());
+
+        } finally {
+            getRealm().setRegistrationEmailAsUsername(false);
+        }
     }
 
     @Test
@@ -313,7 +385,7 @@ public abstract class AbstractIdentityProviderTest {
 
         identityProviderModel.setStoreToken(true);
 
-        authenticateWithIdentityProvider(identityProviderModel);
+        authenticateWithIdentityProvider(identityProviderModel, "test-user");
 
         UserModel federatedUser = getFederatedUser();
         RealmModel realm = getRealm();
@@ -435,8 +507,8 @@ public abstract class AbstractIdentityProviderTest {
 
     protected abstract void doAssertTokenRetrieval(String pageSource);
 
-    private void assertSuccessfulAuthentication(IdentityProviderModel identityProviderModel) {
-        authenticateWithIdentityProvider(identityProviderModel);
+    private void assertSuccessfulAuthentication(IdentityProviderModel identityProviderModel, String username) {
+        authenticateWithIdentityProvider(identityProviderModel, username);
 
         // authenticated and redirected to app
         assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
@@ -464,7 +536,7 @@ public abstract class AbstractIdentityProviderTest {
         assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login"));
     }
 
-    private void authenticateWithIdentityProvider(IdentityProviderModel identityProviderModel) {
+    private void authenticateWithIdentityProvider(IdentityProviderModel identityProviderModel, String username) {
         driver.navigate().to("http://localhost:8081/test-app");
 
         assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login"));
@@ -475,7 +547,7 @@ public abstract class AbstractIdentityProviderTest {
         assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
 
         // log in to identity provider
-        this.loginPage.login("test-user", "password");
+        this.loginPage.login(username, "password");
 
         doAfterProviderAuthentication();
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java
index 2c2b802..2d0657a 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java
@@ -193,4 +193,76 @@ public class RegisterTest {
         events.expectLogin().detail("username", "registerUserSuccess").user(userId).assertEvent();
     }
 
+    @Test
+    public void registerExistingUser_emailAsUsername() {
+        configureRelamRegistrationEmailAsUsername(true);
+
+        try {
+            loginPage.open();
+            loginPage.clickRegister();
+            registerPage.assertCurrent();
+
+            registerPage.registerWithEmailAsUsername("firstName", "lastName", "test-user@localhost", "password", "password");
+
+            registerPage.assertCurrent();
+            Assert.assertEquals("Username already exists", registerPage.getError());
+
+            events.expectRegister("test-user@localhost", "test-user@localhost").user((String) null).error("username_in_use").assertEvent();
+        } finally {
+            configureRelamRegistrationEmailAsUsername(false);
+        }
+    }
+
+    @Test
+    public void registerUserMissingOrInvalidEmail_emailAsUsername() {
+        configureRelamRegistrationEmailAsUsername(true);
+
+        try {
+            loginPage.open();
+            loginPage.clickRegister();
+            registerPage.assertCurrent();
+
+            registerPage.registerWithEmailAsUsername("firstName", "lastName", null, "password", "password");
+            registerPage.assertCurrent();
+            Assert.assertEquals("Please specify email", registerPage.getError());
+            events.expectRegister(null, null).removeDetail("username").removeDetail("email").error("invalid_registration").assertEvent();
+
+            registerPage.registerWithEmailAsUsername("firstName", "lastName", "registerUserInvalidEmailemail", "password", "password");
+            registerPage.assertCurrent();
+            Assert.assertEquals("Invalid email address", registerPage.getError());
+            events.expectRegister("registerUserInvalidEmailemail", "registerUserInvalidEmailemail").error("invalid_registration").assertEvent();
+        } finally {
+            configureRelamRegistrationEmailAsUsername(false);
+        }
+    }
+
+    @Test
+    public void registerUserSuccess_emailAsUsername() {
+        configureRelamRegistrationEmailAsUsername(true);
+        
+        try {
+            loginPage.open();
+            loginPage.clickRegister();
+            registerPage.assertCurrent();
+
+            registerPage.registerWithEmailAsUsername("firstName", "lastName", "registerUserSuccessE@email", "password", "password");
+
+            Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+            String userId = events.expectRegister("registerUserSuccessE@email", "registerUserSuccessE@email").assertEvent().getUserId();
+            events.expectLogin().detail("username", "registerUserSuccessE@email").user(userId).assertEvent();
+        } finally {
+            configureRelamRegistrationEmailAsUsername(false);
+        }
+    }
+
+    protected void configureRelamRegistrationEmailAsUsername(final boolean value) {
+        keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                appRealm.setRegistrationEmailAsUsername(value);
+            }
+        });
+    }
+
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ModelTest.java
index 30807a4..b427a16 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ModelTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ModelTest.java
@@ -17,6 +17,7 @@ public class ModelTest extends AbstractModelTest {
     public void importExportRealm() {
         RealmModel realm = realmManager.createRealm("original");
         realm.setRegistrationAllowed(true);
+        realm.setRegistrationEmailAsUsername(true);
         realm.setResetPasswordAllowed(true);
         realm.setSslRequired(SslRequired.EXTERNAL);
         realm.setVerifyEmail(true);
@@ -47,6 +48,7 @@ public class ModelTest extends AbstractModelTest {
 
     public static void assertEquals(RealmModel expected, RealmModel actual) {
         Assert.assertEquals(expected.isRegistrationAllowed(), actual.isRegistrationAllowed());
+        Assert.assertEquals(expected.isRegistrationEmailAsUsername(), actual.isRegistrationEmailAsUsername());
         Assert.assertEquals(expected.isResetPasswordAllowed(), actual.isResetPasswordAllowed());
         Assert.assertEquals(expected.getSslRequired(), actual.getSslRequired());
         Assert.assertEquals(expected.isVerifyEmail(), actual.isVerifyEmail());
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java
index d3b95ed..217cf19 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java
@@ -21,6 +21,9 @@
  */
 package org.keycloak.testsuite.pages;
 
+import org.junit.Assert;
+
+import org.openqa.selenium.NoSuchElementException;
 import org.openqa.selenium.WebElement;
 import org.openqa.selenium.support.FindBy;
 
@@ -87,6 +90,42 @@ public class RegisterPage extends AbstractPage {
         submitButton.click();
     }
 
+    public void registerWithEmailAsUsername(String firstName, String lastName, String email, String password, String passwordConfirm) {
+        firstNameInput.clear();
+        if (firstName != null) {
+            firstNameInput.sendKeys(firstName);
+        }
+
+        lastNameInput.clear();
+        if (lastName != null) {
+            lastNameInput.sendKeys(lastName);
+        }
+
+        emailInput.clear();
+        if (email != null) {
+            emailInput.sendKeys(email);
+        }
+
+        try {
+            usernameInput.clear();
+            Assert.fail("Form must be without username field");
+        } catch (NoSuchElementException e) {
+            // OK
+        }
+
+        passwordInput.clear();
+        if (password != null) {
+            passwordInput.sendKeys(password);
+        }
+
+        passwordConfirmInput.clear();
+        if (passwordConfirm != null) {
+            passwordConfirmInput.sendKeys(passwordConfirm);
+        }
+
+        submitButton.click();
+    }
+
     public String getError() {
         return loginErrorMessage != null ? loginErrorMessage.getText() : null;
     }
diff --git a/testsuite/integration/src/test/resources/admin-test/testrealm.json b/testsuite/integration/src/test/resources/admin-test/testrealm.json
index 55a2fe1..e4adb40 100755
--- a/testsuite/integration/src/test/resources/admin-test/testrealm.json
+++ b/testsuite/integration/src/test/resources/admin-test/testrealm.json
@@ -3,6 +3,7 @@
     "enabled": true,
     "sslRequired": "external",
     "registrationAllowed": true,
+    "registrationEmailAsUsername": true,
     "resetPasswordAllowed": 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",
diff --git a/testsuite/integration/src/test/resources/broker-test/test-broker-realm-with-kc-oidc.json b/testsuite/integration/src/test/resources/broker-test/test-broker-realm-with-kc-oidc.json
index dbed987..1fd510f 100755
--- a/testsuite/integration/src/test/resources/broker-test/test-broker-realm-with-kc-oidc.json
+++ b/testsuite/integration/src/test/resources/broker-test/test-broker-realm-with-kc-oidc.json
@@ -35,6 +35,17 @@
             "realmRoles": ["manager"]
         },
         {
+            "username" : "test-user-noemail",
+            "enabled": true,
+            "firstName" : "Test",
+            "lastName" : "User",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "realmRoles": ["manager"]
+        },
+        {
           "username" : "pedroigor",
           "enabled": true,
           "email" : "psilva@redhat.com",
diff --git a/testsuite/integration/src/test/resources/broker-test/test-broker-realm-with-saml.json b/testsuite/integration/src/test/resources/broker-test/test-broker-realm-with-saml.json
index 1972224..60c0396 100755
--- a/testsuite/integration/src/test/resources/broker-test/test-broker-realm-with-saml.json
+++ b/testsuite/integration/src/test/resources/broker-test/test-broker-realm-with-saml.json
@@ -32,6 +32,17 @@
         "realmRoles": ["manager"]
       },
       {
+            "username" : "test-user-noemail",
+            "enabled": true,
+            "firstName" : "Test",
+            "lastName" : "User",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "realmRoles": ["manager"]
+      },
+      {
         "username" : "pedroigor",
         "enabled": true,
         "email" : "psilva@redhat.com",
diff --git a/testsuite/integration/src/test/resources/broker-test/test-broker-realm-with-saml-with-signature.json b/testsuite/integration/src/test/resources/broker-test/test-broker-realm-with-saml-with-signature.json
index 3d22c42..4b3c505 100755
--- a/testsuite/integration/src/test/resources/broker-test/test-broker-realm-with-saml-with-signature.json
+++ b/testsuite/integration/src/test/resources/broker-test/test-broker-realm-with-saml-with-signature.json
@@ -38,6 +38,17 @@
         "realmRoles": ["manager"]
       },
       {
+            "username" : "test-user-noemail",
+            "enabled": true,
+            "firstName" : "Test",
+            "lastName" : "User",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "realmRoles": ["manager"]
+      },
+      {
         "username" : "pedroigor",
         "enabled": true,
         "email" : "psilva@redhat.com",