keycloak-aplcache
Changes
forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-login-settings.html 9(+8 -1)
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java 6(+6 -0)
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java 12(+12 -0)
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",