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 f751cf9351..e4ac27f406 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/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index 4212e3babf..ab260a13a3 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/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
index 17792e57ef..38baa89319 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/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index d0963dfb4f..686eede868 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 90b00dd196..ce87adae13 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 c2cfab83f6..5183b74735 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
@@ -152,6 +152,16 @@ public class RealmAdapter implements RealmModel {
realm.setRegistrationAllowed(registrationAllowed);
}
+ @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/RealmAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
index 57013cfbd2..a58774d369 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
@@ -108,6 +108,18 @@ public class RealmAdapter implements RealmModel {
updated.setRegistrationAllowed(registrationAllowed);
}
+ @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();
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 8089e4eb94..dc062401c6 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/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 9c0e75135f..a17dd1791d 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
@@ -123,6 +123,17 @@ public class RealmAdapter implements RealmModel {
em.flush();
}
+ @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/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 9a4358b216..1ba7a5573c 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/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 897ac6f836..9bee5a52c0 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 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 655b14896e..252a081df4 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 d26f695dd8..c95b61807f 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 f62bd5c551..0198644bd7 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 a5a0f3f188..254cee1e8c 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 formData, List requiredCredentialTypes) {
+ public static String validateRegistrationForm(RealmModel realm, MultivaluedMap formData, List 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 100f9c1f90..302dd8da86 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 5dd37bc03d..564cb85f45 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 dd034c1e82..b1ae4cbe70 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 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 2c2b802b06..2d0657a438 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 30807a4f08..b427a1644c 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 d3b95ed01f..217cf19022 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 55a2fe1bce..e4adb407bc 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 dbed98718d..1fd510f7e6 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
@@ -34,6 +34,17 @@
],
"realmRoles": ["manager"]
},
+ {
+ "username" : "test-user-noemail",
+ "enabled": true,
+ "firstName" : "Test",
+ "lastName" : "User",
+ "credentials" : [
+ { "type" : "password",
+ "value" : "password" }
+ ],
+ "realmRoles": ["manager"]
+ },
{
"username" : "pedroigor",
"enabled": true,
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 3d22c42546..4b3c50518e 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
@@ -37,6 +37,17 @@
],
"realmRoles": ["manager"]
},
+ {
+ "username" : "test-user-noemail",
+ "enabled": true,
+ "firstName" : "Test",
+ "lastName" : "User",
+ "credentials" : [
+ { "type" : "password",
+ "value" : "password" }
+ ],
+ "realmRoles": ["manager"]
+ },
{
"username" : "pedroigor",
"enabled": true,
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 19722246ef..60c0396094 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
@@ -31,6 +31,17 @@
],
"realmRoles": ["manager"]
},
+ {
+ "username" : "test-user-noemail",
+ "enabled": true,
+ "firstName" : "Test",
+ "lastName" : "User",
+ "credentials" : [
+ { "type" : "password",
+ "value" : "password" }
+ ],
+ "realmRoles": ["manager"]
+ },
{
"username" : "pedroigor",
"enabled": true,