Merge pull request #1034 from velias/KEYCLOAK-1074

#1074 - Allow registration with email as username
This commit is contained in:
Bill Burke 2015-03-12 15:36:05 -04:00
commit 7d2a6237b0
32 changed files with 368 additions and 15 deletions

View file

@ -93,6 +93,7 @@
<addColumn tableName="REALM"> <addColumn tableName="REALM">
<column name="LOGIN_LIFESPAN" type="INT"/> <column name="LOGIN_LIFESPAN" type="INT"/>
<column name="REGISTRATION_EMAIL_AS_USERNAME" type="BOOLEAN" defaultValueBoolean="false"/>
</addColumn> </addColumn>
</changeSet> </changeSet>
</databaseChangeLog> </databaseChangeLog>

View file

@ -24,6 +24,7 @@ public class RealmRepresentation {
protected String sslRequired; protected String sslRequired;
protected Boolean passwordCredentialGrantAllowed; protected Boolean passwordCredentialGrantAllowed;
protected Boolean registrationAllowed; protected Boolean registrationAllowed;
protected Boolean registrationEmailAsUsername;
protected Boolean rememberMe; protected Boolean rememberMe;
protected Boolean verifyEmail; protected Boolean verifyEmail;
protected Boolean resetPasswordAllowed; protected Boolean resetPasswordAllowed;
@ -264,6 +265,14 @@ public class RealmRepresentation {
this.registrationAllowed = registrationAllowed; this.registrationAllowed = registrationAllowed;
} }
public Boolean isRegistrationEmailAsUsername() {
return registrationEmailAsUsername;
}
public void setRegistrationEmailAsUsername(Boolean registrationEmailAsUsername) {
this.registrationEmailAsUsername = registrationEmailAsUsername;
}
public Boolean isRememberMe() { public Boolean isRememberMe() {
return rememberMe; return rememberMe;
} }

View file

@ -41,6 +41,7 @@ public interface Errors {
String NOT_ALLOWED = "not_allowed"; String NOT_ALLOWED = "not_allowed";
String FEDERATED_IDENTITY_EMAIL_EXISTS = "federated_identity_email_exists"; 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 FEDERATED_IDENTITY_USERNAME_EXISTS = "federated_identity_username_exists";
String SSL_REQUIRED = "ssl_required"; String SSL_REQUIRED = "ssl_required";

View file

@ -14,6 +14,13 @@
</div> </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>
<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"> <div class="form-group">
<label for="resetPasswordAllowed" class="col-sm-2 control-label">Forget password</label> <label for="resetPasswordAllowed" class="col-sm-2 control-label">Forget password</label>
<div class="col-sm-4"> <div class="col-sm-4">

View file

@ -17,7 +17,7 @@
<form id="kc-form-login" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post"> <form id="kc-form-login" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
<div class="${properties.kcFormGroupClass!}"> <div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcLabelWrapperClass!}"> <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>
<div class="${properties.kcInputWrapperClass!}"> <div class="${properties.kcInputWrapperClass!}">

View file

@ -62,6 +62,7 @@ emailExists=Email already exists
federatedIdentityEmailExists=User with email already exists. Please login to account management to link the account. 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. 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 loginTitle=Log in to
loginOauthTitle=Temporary access. loginOauthTitle=Temporary access.

View file

@ -6,6 +6,7 @@
${rb.registerWith} <strong>${realm.name}</strong> ${rb.registerWith} <strong>${realm.name}</strong>
<#elseif section = "form"> <#elseif section = "form">
<form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.registrationAction}" method="post"> <form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.registrationAction}" method="post">
<#if !realm.registrationEmailAsUsername>
<div class="${properties.kcFormGroupClass!}"> <div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcLabelWrapperClass!}"> <div class="${properties.kcLabelWrapperClass!}">
<label for="username" class="${properties.kcLabelClass!}">${rb.username}</label> <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}" /> <input type="text" id="username" class="${properties.kcInputClass!}" name="username" value="${(register.formData.username!'')?html}" />
</div> </div>
</div> </div>
</#if>
<div class="${properties.kcFormGroupClass!}"> <div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcLabelWrapperClass!}"> <div class="${properties.kcLabelWrapperClass!}">
<label for="firstName" class="${properties.kcLabelClass!}">${rb.firstName}</label> <label for="firstName" class="${properties.kcLabelClass!}">${rb.firstName}</label>

View file

@ -48,6 +48,10 @@ public class RealmBean {
return realm.isRegistrationAllowed(); return realm.isRegistrationAllowed();
} }
public boolean isRegistrationEmailAsUsername() {
return realm.isRegistrationEmailAsUsername();
}
public boolean isResetPasswordAllowed() { public boolean isResetPasswordAllowed() {
return realm.isResetPasswordAllowed(); return realm.isResetPasswordAllowed();
} }

View file

@ -47,6 +47,10 @@ public interface RealmModel extends RoleContainerModel {
void setRegistrationAllowed(boolean registrationAllowed); void setRegistrationAllowed(boolean registrationAllowed);
public boolean isRegistrationEmailAsUsername();
public void setRegistrationEmailAsUsername(boolean registrationEmailAsUsername);
boolean isPasswordCredentialGrantAllowed(); boolean isPasswordCredentialGrantAllowed();
void setPasswordCredentialGrantAllowed(boolean passwordCredentialGrantAllowed); void setPasswordCredentialGrantAllowed(boolean passwordCredentialGrantAllowed);

View file

@ -14,6 +14,7 @@ public class RealmEntity extends AbstractIdentifiableEntity {
private boolean enabled; private boolean enabled;
private String sslRequired; private String sslRequired;
private boolean registrationAllowed; private boolean registrationAllowed;
protected boolean registrationEmailAsUsername;
private boolean rememberMe; private boolean rememberMe;
private boolean verifyEmail; private boolean verifyEmail;
private boolean passwordCredentialGrantAllowed; private boolean passwordCredentialGrantAllowed;
@ -104,6 +105,14 @@ public class RealmEntity extends AbstractIdentifiableEntity {
this.registrationAllowed = registrationAllowed; this.registrationAllowed = registrationAllowed;
} }
public boolean isRegistrationEmailAsUsername() {
return registrationEmailAsUsername;
}
public void setRegistrationEmailAsUsername(boolean registrationEmailAsUsername) {
this.registrationEmailAsUsername = registrationEmailAsUsername;
}
public boolean isRememberMe() { public boolean isRememberMe() {
return rememberMe; return rememberMe;
} }

View file

@ -99,6 +99,7 @@ public class ModelToRepresentation {
rep.setCertificate(realm.getCertificatePem()); rep.setCertificate(realm.getCertificatePem());
rep.setPasswordCredentialGrantAllowed(realm.isPasswordCredentialGrantAllowed()); rep.setPasswordCredentialGrantAllowed(realm.isPasswordCredentialGrantAllowed());
rep.setRegistrationAllowed(realm.isRegistrationAllowed()); rep.setRegistrationAllowed(realm.isRegistrationAllowed());
rep.setRegistrationEmailAsUsername(realm.isRegistrationEmailAsUsername());
rep.setRememberMe(realm.isRememberMe()); rep.setRememberMe(realm.isRememberMe());
rep.setBruteForceProtected(realm.isBruteForceProtected()); rep.setBruteForceProtected(realm.isBruteForceProtected());
rep.setMaxFailureWaitSeconds(realm.getMaxFailureWaitSeconds()); rep.setMaxFailureWaitSeconds(realm.getMaxFailureWaitSeconds());

View file

@ -85,6 +85,8 @@ public class RepresentationToModel {
if (rep.getSslRequired() != null) newRealm.setSslRequired(SslRequired.valueOf(rep.getSslRequired().toUpperCase())); if (rep.getSslRequired() != null) newRealm.setSslRequired(SslRequired.valueOf(rep.getSslRequired().toUpperCase()));
if (rep.isPasswordCredentialGrantAllowed() != null) newRealm.setPasswordCredentialGrantAllowed(rep.isPasswordCredentialGrantAllowed()); if (rep.isPasswordCredentialGrantAllowed() != null) newRealm.setPasswordCredentialGrantAllowed(rep.isPasswordCredentialGrantAllowed());
if (rep.isRegistrationAllowed() != null) newRealm.setRegistrationAllowed(rep.isRegistrationAllowed()); 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.isRememberMe() != null) newRealm.setRememberMe(rep.isRememberMe());
if (rep.isVerifyEmail() != null) newRealm.setVerifyEmail(rep.isVerifyEmail()); if (rep.isVerifyEmail() != null) newRealm.setVerifyEmail(rep.isVerifyEmail());
if (rep.isResetPasswordAllowed() != null) newRealm.setResetPasswordAllowed(rep.isResetPasswordAllowed()); 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.getFailureFactor() != null) realm.setFailureFactor(rep.getFailureFactor());
if (rep.isPasswordCredentialGrantAllowed() != null) realm.setPasswordCredentialGrantAllowed(rep.isPasswordCredentialGrantAllowed()); if (rep.isPasswordCredentialGrantAllowed() != null) realm.setPasswordCredentialGrantAllowed(rep.isPasswordCredentialGrantAllowed());
if (rep.isRegistrationAllowed() != null) realm.setRegistrationAllowed(rep.isRegistrationAllowed()); 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.isRememberMe() != null) realm.setRememberMe(rep.isRememberMe());
if (rep.isVerifyEmail() != null) realm.setVerifyEmail(rep.isVerifyEmail()); if (rep.isVerifyEmail() != null) realm.setVerifyEmail(rep.isVerifyEmail());
if (rep.isResetPasswordAllowed() != null) realm.setResetPasswordAllowed(rep.isResetPasswordAllowed()); if (rep.isResetPasswordAllowed() != null) realm.setResetPasswordAllowed(rep.isResetPasswordAllowed());

View file

@ -152,6 +152,16 @@ public class RealmAdapter implements RealmModel {
realm.setRegistrationAllowed(registrationAllowed); realm.setRegistrationAllowed(registrationAllowed);
} }
@Override
public boolean isRegistrationEmailAsUsername() {
return realm.isRegistrationEmailAsUsername();
}
@Override
public void setRegistrationEmailAsUsername(boolean registrationEmailAsUsername) {
realm.setRegistrationEmailAsUsername(registrationEmailAsUsername);
}
@Override @Override
public boolean isRememberMe() { public boolean isRememberMe() {
return realm.isRememberMe(); return realm.isRememberMe();

View file

@ -108,6 +108,18 @@ public class RealmAdapter implements RealmModel {
updated.setRegistrationAllowed(registrationAllowed); 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 @Override
public boolean isPasswordCredentialGrantAllowed() { public boolean isPasswordCredentialGrantAllowed() {
if (updated != null) return updated.isPasswordCredentialGrantAllowed(); if (updated != null) return updated.isPasswordCredentialGrantAllowed();

View file

@ -33,6 +33,7 @@ public class CachedRealm {
private boolean enabled; private boolean enabled;
private SslRequired sslRequired; private SslRequired sslRequired;
private boolean registrationAllowed; private boolean registrationAllowed;
private boolean registrationEmailAsUsername;
private boolean rememberMe; private boolean rememberMe;
private boolean verifyEmail; private boolean verifyEmail;
private boolean passwordCredentialGrantAllowed; private boolean passwordCredentialGrantAllowed;
@ -92,6 +93,7 @@ public class CachedRealm {
enabled = model.isEnabled(); enabled = model.isEnabled();
sslRequired = model.getSslRequired(); sslRequired = model.getSslRequired();
registrationAllowed = model.isRegistrationAllowed(); registrationAllowed = model.isRegistrationAllowed();
registrationEmailAsUsername = model.isRegistrationEmailAsUsername();
rememberMe = model.isRememberMe(); rememberMe = model.isRememberMe();
verifyEmail = model.isVerifyEmail(); verifyEmail = model.isVerifyEmail();
passwordCredentialGrantAllowed = model.isPasswordCredentialGrantAllowed(); passwordCredentialGrantAllowed = model.isPasswordCredentialGrantAllowed();
@ -205,6 +207,10 @@ public class CachedRealm {
return registrationAllowed; return registrationAllowed;
} }
public boolean isRegistrationEmailAsUsername() {
return registrationEmailAsUsername;
}
public boolean isPasswordCredentialGrantAllowed() { public boolean isPasswordCredentialGrantAllowed() {
return passwordCredentialGrantAllowed; return passwordCredentialGrantAllowed;
} }

View file

@ -123,6 +123,17 @@ public class RealmAdapter implements RealmModel {
em.flush(); em.flush();
} }
@Override
public boolean isRegistrationEmailAsUsername() {
return realm.isRegistrationEmailAsUsername();
}
@Override
public void setRegistrationEmailAsUsername(boolean registrationEmailAsUsername) {
realm.setRegistrationEmailAsUsername(registrationEmailAsUsername);
em.flush();
}
@Override @Override
public boolean isRememberMe() { public boolean isRememberMe() {
return realm.isRememberMe(); return realm.isRememberMe();

View file

@ -47,6 +47,8 @@ public class RealmEntity {
protected String sslRequired; protected String sslRequired;
@Column(name="REGISTRATION_ALLOWED") @Column(name="REGISTRATION_ALLOWED")
protected boolean registrationAllowed; protected boolean registrationAllowed;
@Column(name = "REGISTRATION_EMAIL_AS_USERNAME")
protected boolean registrationEmailAsUsername;
@Column(name="PASSWORD_CRED_GRANT_ALLOWED") @Column(name="PASSWORD_CRED_GRANT_ALLOWED")
protected boolean passwordCredentialGrantAllowed; protected boolean passwordCredentialGrantAllowed;
@Column(name="VERIFY_EMAIL") @Column(name="VERIFY_EMAIL")
@ -183,6 +185,14 @@ public class RealmEntity {
this.registrationAllowed = registrationAllowed; this.registrationAllowed = registrationAllowed;
} }
public boolean isRegistrationEmailAsUsername() {
return registrationEmailAsUsername;
}
public void setRegistrationEmailAsUsername(boolean registrationEmailAsUsername) {
this.registrationEmailAsUsername = registrationEmailAsUsername;
}
public boolean isRememberMe() { public boolean isRememberMe() {
return rememberMe; return rememberMe;
} }

View file

@ -123,6 +123,15 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
updateRealm(); updateRealm();
} }
public boolean isRegistrationEmailAsUsername() {
return realm.isRegistrationEmailAsUsername();
}
public void setRegistrationEmailAsUsername(boolean registrationEmailAsUsername) {
realm.setRegistrationEmailAsUsername(registrationEmailAsUsername);
updateRealm();
}
@Override @Override
public boolean isRememberMe() { public boolean isRememberMe() {
return realm.isRememberMe(); return realm.isRememberMe();

View file

@ -56,6 +56,7 @@ public class ApplianceBootstrap {
realm.setAccessCodeLifespanUserAction(300); realm.setAccessCodeLifespanUserAction(300);
realm.setSslRequired(SslRequired.EXTERNAL); realm.setSslRequired(SslRequired.EXTERNAL);
realm.setRegistrationAllowed(false); realm.setRegistrationAllowed(false);
realm.setRegistrationEmailAsUsername(false);
KeycloakModelUtils.generateRealmKeys(realm); KeycloakModelUtils.generateRealmKeys(realm);
UserModel adminUser = session.users().addUser(realm, "admin"); UserModel adminUser = session.users().addUser(realm, "admin");

View file

@ -538,7 +538,20 @@ public class IdentityBrokerService {
throw new IdentityBrokerException("federatedIdentityEmailExists"); 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) { if (existingUser != null) {
fireErrorEvent(Errors.FEDERATED_IDENTITY_USERNAME_EXISTS); fireErrorEvent(Errors.FEDERATED_IDENTITY_USERNAME_EXISTS);
@ -549,7 +562,7 @@ public class IdentityBrokerService {
LOGGER.debugf("Creating account from identity [%s].", federatedIdentityModel); 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()) { if (isDebugEnabled()) {
LOGGER.debugf("Account [%s] created.", federatedUser); LOGGER.debugf("Account [%s] created.", federatedUser);

View file

@ -430,6 +430,10 @@ public class LoginActionsService {
String username = formData.getFirst("username"); String username = formData.getFirst("username");
String email = formData.getFirst("email"); String email = formData.getFirst("email");
if (realm.isRegistrationEmailAsUsername()) {
username = email;
formData.putSingle(AuthenticationManager.FORM_USERNAME, username);
}
ClientSessionModel clientSession = clientCode.getClientSession(); ClientSessionModel clientSession = clientCode.getClientSession();
event.client(clientSession.getClient()) event.client(clientSession.getClient())
.detail(Details.REDIRECT_URI, clientSession.getRedirectUri()) .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 // 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) { if (error == null) {
error = Validation.validatePassword(formData, realm.getPasswordPolicy()); error = Validation.validatePassword(formData, realm.getPasswordPolicy());
} }

View file

@ -1,6 +1,7 @@
package org.keycloak.services.validation; package org.keycloak.services.validation;
import org.keycloak.models.PasswordPolicy; import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.messages.Messages; import org.keycloak.services.messages.Messages;
@ -13,7 +14,7 @@ public class Validation {
// Actually allow same emails like angular. See ValidationTest.testEmailValidation() // 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-]+)*"); 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"))) { if (isEmpty(formData.getFirst("firstName"))) {
return Messages.MISSING_FIRST_NAME; return Messages.MISSING_FIRST_NAME;
} }
@ -30,7 +31,7 @@ public class Validation {
return Messages.INVALID_EMAIL; return Messages.INVALID_EMAIL;
} }
if (isEmpty(formData.getFirst("username"))) { if (!realm.isRegistrationEmailAsUsername() && isEmpty(formData.getFirst("username"))) {
return Messages.MISSING_USERNAME; return Messages.MISSING_USERNAME;
} }

View file

@ -233,6 +233,7 @@ public class AdminAPITest {
if (rep.getFailureFactor() != null) Assert.assertEquals(rep.getFailureFactor(), storedRealm.getFailureFactor()); if (rep.getFailureFactor() != null) Assert.assertEquals(rep.getFailureFactor(), storedRealm.getFailureFactor());
if (rep.isPasswordCredentialGrantAllowed() != null) Assert.assertEquals(rep.isPasswordCredentialGrantAllowed(), storedRealm.isPasswordCredentialGrantAllowed()); if (rep.isPasswordCredentialGrantAllowed() != null) Assert.assertEquals(rep.isPasswordCredentialGrantAllowed(), storedRealm.isPasswordCredentialGrantAllowed());
if (rep.isRegistrationAllowed() != null) Assert.assertEquals(rep.isRegistrationAllowed(), storedRealm.isRegistrationAllowed()); 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.isRememberMe() != null) Assert.assertEquals(rep.isRememberMe(), storedRealm.isRememberMe());
if (rep.isVerifyEmail() != null) Assert.assertEquals(rep.isVerifyEmail(), storedRealm.isVerifyEmail()); if (rep.isVerifyEmail() != null) Assert.assertEquals(rep.isVerifyEmail(), storedRealm.isVerifyEmail());
if (rep.isResetPasswordAllowed() != null) Assert.assertEquals(rep.isResetPasswordAllowed(), storedRealm.isResetPasswordAllowed()); if (rep.isResetPasswordAllowed() != null) Assert.assertEquals(rep.isResetPasswordAllowed(), storedRealm.isResetPasswordAllowed());

View file

@ -60,10 +60,13 @@ public class RealmTest extends AbstractClientTest {
@Test @Test
public void updateRealm() { public void updateRealm() {
// first change
RealmRepresentation rep = realm.toRepresentation(); RealmRepresentation rep = realm.toRepresentation();
rep.setSsoSessionIdleTimeout(123); rep.setSsoSessionIdleTimeout(123);
rep.setSsoSessionMaxLifespan(12); rep.setSsoSessionMaxLifespan(12);
rep.setAccessCodeLifespanLogin(1234); rep.setAccessCodeLifespanLogin(1234);
rep.setRegistrationAllowed(true);
rep.setRegistrationEmailAsUsername(true);
realm.update(rep); realm.update(rep);
@ -72,6 +75,18 @@ public class RealmTest extends AbstractClientTest {
assertEquals(123, rep.getSsoSessionIdleTimeout().intValue()); assertEquals(123, rep.getSsoSessionIdleTimeout().intValue());
assertEquals(12, rep.getSsoSessionMaxLifespan().intValue()); assertEquals(12, rep.getSsoSessionMaxLifespan().intValue());
assertEquals(1234, rep.getAccessCodeLifespanLogin().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 @Test

View file

@ -59,6 +59,7 @@ import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriBuilder;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.util.List; 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.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
/** /**
@ -124,7 +126,7 @@ public abstract class AbstractIdentityProviderTest {
public void testSuccessfulAuthentication() { public void testSuccessfulAuthentication() {
IdentityProviderModel identityProviderModel = getIdentityProviderModel(); IdentityProviderModel identityProviderModel = getIdentityProviderModel();
assertSuccessfulAuthentication(identityProviderModel); assertSuccessfulAuthentication(identityProviderModel, "test-user");
} }
@Test @Test
@ -132,7 +134,77 @@ public abstract class AbstractIdentityProviderTest {
IdentityProviderModel identityProviderModel = getIdentityProviderModel(); IdentityProviderModel identityProviderModel = getIdentityProviderModel();
identityProviderModel.setUpdateProfileFirstLogin(false); 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 @Test
@ -313,7 +385,7 @@ public abstract class AbstractIdentityProviderTest {
identityProviderModel.setStoreToken(true); identityProviderModel.setStoreToken(true);
authenticateWithIdentityProvider(identityProviderModel); authenticateWithIdentityProvider(identityProviderModel, "test-user");
UserModel federatedUser = getFederatedUser(); UserModel federatedUser = getFederatedUser();
RealmModel realm = getRealm(); RealmModel realm = getRealm();
@ -435,8 +507,8 @@ public abstract class AbstractIdentityProviderTest {
protected abstract void doAssertTokenRetrieval(String pageSource); protected abstract void doAssertTokenRetrieval(String pageSource);
private void assertSuccessfulAuthentication(IdentityProviderModel identityProviderModel) { private void assertSuccessfulAuthentication(IdentityProviderModel identityProviderModel, String username) {
authenticateWithIdentityProvider(identityProviderModel); authenticateWithIdentityProvider(identityProviderModel, username);
// authenticated and redirected to app // authenticated and redirected to app
assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-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")); 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"); 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")); 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/")); assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
// log in to identity provider // log in to identity provider
this.loginPage.login("test-user", "password"); this.loginPage.login(username, "password");
doAfterProviderAuthentication(); doAfterProviderAuthentication();

View file

@ -193,4 +193,76 @@ public class RegisterTest {
events.expectLogin().detail("username", "registerUserSuccess").user(userId).assertEvent(); 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);
}
});
}
} }

View file

@ -17,6 +17,7 @@ public class ModelTest extends AbstractModelTest {
public void importExportRealm() { public void importExportRealm() {
RealmModel realm = realmManager.createRealm("original"); RealmModel realm = realmManager.createRealm("original");
realm.setRegistrationAllowed(true); realm.setRegistrationAllowed(true);
realm.setRegistrationEmailAsUsername(true);
realm.setResetPasswordAllowed(true); realm.setResetPasswordAllowed(true);
realm.setSslRequired(SslRequired.EXTERNAL); realm.setSslRequired(SslRequired.EXTERNAL);
realm.setVerifyEmail(true); realm.setVerifyEmail(true);
@ -47,6 +48,7 @@ public class ModelTest extends AbstractModelTest {
public static void assertEquals(RealmModel expected, RealmModel actual) { public static void assertEquals(RealmModel expected, RealmModel actual) {
Assert.assertEquals(expected.isRegistrationAllowed(), actual.isRegistrationAllowed()); Assert.assertEquals(expected.isRegistrationAllowed(), actual.isRegistrationAllowed());
Assert.assertEquals(expected.isRegistrationEmailAsUsername(), actual.isRegistrationEmailAsUsername());
Assert.assertEquals(expected.isResetPasswordAllowed(), actual.isResetPasswordAllowed()); Assert.assertEquals(expected.isResetPasswordAllowed(), actual.isResetPasswordAllowed());
Assert.assertEquals(expected.getSslRequired(), actual.getSslRequired()); Assert.assertEquals(expected.getSslRequired(), actual.getSslRequired());
Assert.assertEquals(expected.isVerifyEmail(), actual.isVerifyEmail()); Assert.assertEquals(expected.isVerifyEmail(), actual.isVerifyEmail());

View file

@ -21,6 +21,9 @@
*/ */
package org.keycloak.testsuite.pages; package org.keycloak.testsuite.pages;
import org.junit.Assert;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.FindBy;
@ -87,6 +90,42 @@ public class RegisterPage extends AbstractPage {
submitButton.click(); 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() { public String getError() {
return loginErrorMessage != null ? loginErrorMessage.getText() : null; return loginErrorMessage != null ? loginErrorMessage.getText() : null;
} }

View file

@ -3,6 +3,7 @@
"enabled": true, "enabled": true,
"sslRequired": "external", "sslRequired": "external",
"registrationAllowed": true, "registrationAllowed": true,
"registrationEmailAsUsername": true,
"resetPasswordAllowed": true, "resetPasswordAllowed": true,
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=", "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", "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",

View file

@ -34,6 +34,17 @@
], ],
"realmRoles": ["manager"] "realmRoles": ["manager"]
}, },
{
"username" : "test-user-noemail",
"enabled": true,
"firstName" : "Test",
"lastName" : "User",
"credentials" : [
{ "type" : "password",
"value" : "password" }
],
"realmRoles": ["manager"]
},
{ {
"username" : "pedroigor", "username" : "pedroigor",
"enabled": true, "enabled": true,

View file

@ -37,6 +37,17 @@
], ],
"realmRoles": ["manager"] "realmRoles": ["manager"]
}, },
{
"username" : "test-user-noemail",
"enabled": true,
"firstName" : "Test",
"lastName" : "User",
"credentials" : [
{ "type" : "password",
"value" : "password" }
],
"realmRoles": ["manager"]
},
{ {
"username" : "pedroigor", "username" : "pedroigor",
"enabled": true, "enabled": true,

View file

@ -31,6 +31,17 @@
], ],
"realmRoles": ["manager"] "realmRoles": ["manager"]
}, },
{
"username" : "test-user-noemail",
"enabled": true,
"firstName" : "Test",
"lastName" : "User",
"credentials" : [
{ "type" : "password",
"value" : "password" }
],
"realmRoles": ["manager"]
},
{ {
"username" : "pedroigor", "username" : "pedroigor",
"enabled": true, "enabled": true,