Merge pull request #1633 from stianst/update-username

KEYCLOAK-1857 Allow users to edit username on first login
This commit is contained in:
Stian Thorgersen 2015-09-23 08:26:03 +02:00
commit 30aa41a2ee
9 changed files with 203 additions and 22 deletions

View file

@ -6,6 +6,16 @@
${msg("loginProfileTitle")}
<#elseif section = "form">
<form id="kc-update-profile-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
<#if realm.editUsernameAllowed>
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('username',properties.kcFormGroupErrorClass!)}">
<div class="${properties.kcLabelWrapperClass!}">
<label for="username" class="${properties.kcLabelClass!}">${msg("username")}</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input type="text" id="username" name="username" value="${(user.username!'')?html}" class="${properties.kcInputClass!}"/>
</div>
</div>
</#if>
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('email',properties.kcFormGroupErrorClass!)}">
<div class="${properties.kcLabelWrapperClass!}">
<label for="email" class="${properties.kcLabelClass!}">${msg("email")}</label>

View file

@ -70,6 +70,8 @@ public class ProfileBean {
}
public String getUsername() { return formData != null ? formData.getFirst("username") : user.getUsername(); }
public String getFirstName() {
return formData != null ? formData.getFirst("firstName") : user.getFirstName();
}

View file

@ -66,6 +66,10 @@ public class RealmBean {
return realm.isInternationalizationEnabled();
}
public boolean isEditUsernameAllowed() {
return realm.isEditUsernameAllowed();
}
public boolean isPassword() {
for (RequiredCredentialModel r : realm.getRequiredCredentials()) {
if (r.getType().equals(CredentialRepresentation.PASSWORD)) {

View file

@ -52,7 +52,7 @@ public class UpdateProfile implements RequiredActionProvider, RequiredActionFact
RealmModel realm = context.getRealm();
List<FormMessage> errors = Validation.validateUpdateProfileForm(formData);
List<FormMessage> errors = Validation.validateUpdateProfileForm(realm, formData);
if (errors != null && !errors.isEmpty()) {
Response challenge = context.form()
.setErrors(errors)
@ -62,6 +62,28 @@ public class UpdateProfile implements RequiredActionProvider, RequiredActionFact
return;
}
if (realm.isEditUsernameAllowed()) {
String username = formData.getFirst("username");
String oldUsername = user.getUsername();
boolean usernameChanged = oldUsername != null ? !oldUsername.equals(username) : username != null;
if (usernameChanged) {
if (session.users().getUserByUsername(username, realm) != null) {
Response challenge = context.form()
.setError(Messages.USERNAME_EXISTS)
.setFormData(formData)
.createResponse(UserModel.RequiredAction.UPDATE_PROFILE);
context.challenge(challenge);
return;
}
user.setUsername(username);
}
}
user.setFirstName(formData.getFirst("firstName"));
user.setLastName(formData.getFirst("lastName"));

View file

@ -33,11 +33,8 @@ import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.*;
import org.keycloak.testsuite.pages.AppPage.RequestType;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
import org.keycloak.testsuite.pages.LoginUpdateProfilePage;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.KeycloakRule.KeycloakSetup;
import org.keycloak.testsuite.rule.WebResource;
@ -83,7 +80,7 @@ public class RequiredActionMultipleActionsTest {
protected LoginPasswordUpdatePage changePasswordPage;
@WebResource
protected LoginUpdateProfilePage updateProfilePage;
protected LoginUpdateProfileEditUsernameAllowedPage updateProfilePage;
@Test
public void updateProfileAndPassword() throws Exception {
@ -121,7 +118,7 @@ public class RequiredActionMultipleActionsTest {
}
public String updateProfile(String sessionId) {
updateProfilePage.update("New first", "New last", "new@email.com");
updateProfilePage.update("New first", "New last", "new@email.com", "test-user@localhost");
AssertEvents.ExpectedEvent expectedEvent = events.expectRequiredAction(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com");
if (sessionId != null) {

View file

@ -21,11 +21,7 @@
*/
package org.keycloak.testsuite.actions;
import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.*;
import org.keycloak.events.Details;
import org.keycloak.events.EventType;
import org.keycloak.models.RealmModel;
@ -36,7 +32,7 @@ import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.AppPage.RequestType;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.LoginUpdateProfilePage;
import org.keycloak.testsuite.pages.LoginUpdateProfileEditUsernameAllowedPage;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
@ -66,7 +62,7 @@ public class RequiredActionUpdateProfileTest {
protected LoginPage loginPage;
@WebResource
protected LoginUpdateProfilePage updateProfilePage;
protected LoginUpdateProfileEditUsernameAllowedPage updateProfilePage;
@Before
public void before() {
@ -75,6 +71,8 @@ public class RequiredActionUpdateProfileTest {
public void config(RealmManager manager, RealmModel defaultRealm, RealmModel appRealm) {
UserModel user = manager.getSession().users().getUserByUsername("test-user@localhost", appRealm);
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PROFILE);
UserModel anotherUser = manager.getSession().users().getUserByEmail("john-doh@localhost", appRealm);
anotherUser.addRequiredAction(UserModel.RequiredAction.UPDATE_PROFILE);
}
});
}
@ -87,7 +85,7 @@ public class RequiredActionUpdateProfileTest {
updateProfilePage.assertCurrent();
updateProfilePage.update("New first", "New last", "new@email.com");
updateProfilePage.update("New first", "New last", "new@email.com", "test-user@localhost");
String sessionId = events.expectRequiredAction(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent().getSessionId();
events.expectRequiredAction(EventType.UPDATE_PROFILE).session(sessionId).assertEvent();
@ -101,6 +99,41 @@ public class RequiredActionUpdateProfileTest {
Assert.assertEquals("New first", user.getFirstName());
Assert.assertEquals("New last", user.getLastName());
Assert.assertEquals("new@email.com", user.getEmail());
Assert.assertEquals("test-user@localhost", user.getUsername());
}
@Test
public void updateUsername() {
loginPage.open();
loginPage.login("john-doh@localhost", "password");
String userId = keycloakRule.getUser("test", "john-doh@localhost").getId();
updateProfilePage.assertCurrent();
updateProfilePage.update("New first", "New last", "john-doh@localhost", "new");
String sessionId = events
.expectLogin()
.event(EventType.UPDATE_PROFILE)
.detail(Details.USERNAME, "john-doh@localhost")
.user(userId)
.session(AssertEvents.isUUID())
.removeDetail(Details.CONSENT)
.assertEvent()
.getSessionId();
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
events.expectLogin().detail(Details.USERNAME, "john-doh@localhost").user(userId).session(sessionId).assertEvent();
// assert user is really updated in persistent store
UserRepresentation user = keycloakRule.getUser("test", "new");
Assert.assertEquals("New first", user.getFirstName());
Assert.assertEquals("New last", user.getLastName());
Assert.assertEquals("john-doh@localhost", user.getEmail());
Assert.assertEquals("new", user.getUsername());
}
@Test
@ -111,7 +144,7 @@ public class RequiredActionUpdateProfileTest {
updateProfilePage.assertCurrent();
updateProfilePage.update("", "New last", "new@email.com");
updateProfilePage.update("", "New last", "new@email.com", "new");
updateProfilePage.assertCurrent();
@ -133,7 +166,7 @@ public class RequiredActionUpdateProfileTest {
updateProfilePage.assertCurrent();
updateProfilePage.update("New first", "", "new@email.com");
updateProfilePage.update("New first", "", "new@email.com", "new");
updateProfilePage.assertCurrent();
@ -155,7 +188,7 @@ public class RequiredActionUpdateProfileTest {
updateProfilePage.assertCurrent();
updateProfilePage.update("New first", "New last", "");
updateProfilePage.update("New first", "New last", "", "new");
updateProfilePage.assertCurrent();
@ -177,7 +210,7 @@ public class RequiredActionUpdateProfileTest {
updateProfilePage.assertCurrent();
updateProfilePage.update("New first", "New last", "invalidemail");
updateProfilePage.update("New first", "New last", "invalidemail", "invalid");
updateProfilePage.assertCurrent();
@ -191,6 +224,52 @@ public class RequiredActionUpdateProfileTest {
events.assertEmpty();
}
@Test
public void updateProfileMissingUsername() {
loginPage.open();
loginPage.login("john-doh@localhost", "password");
updateProfilePage.assertCurrent();
updateProfilePage.update("New first", "New last", "new@email.com", "");
updateProfilePage.assertCurrent();
// assert that form holds submitted values during validation error
Assert.assertEquals("New first", updateProfilePage.getFirstName());
Assert.assertEquals("New last", updateProfilePage.getLastName());
Assert.assertEquals("new@email.com", updateProfilePage.getEmail());
Assert.assertEquals("", updateProfilePage.getUsername());
Assert.assertEquals("Please specify username.", updateProfilePage.getError());
events.assertEmpty();
}
@Test
public void updateProfileDuplicateUsername() {
loginPage.open();
loginPage.login("john-doh@localhost", "password");
updateProfilePage.assertCurrent();
updateProfilePage.update("New first", "New last", "new@email.com", "test-user@localhost");
updateProfilePage.assertCurrent();
// assert that form holds submitted values during validation error
Assert.assertEquals("New first", updateProfilePage.getFirstName());
Assert.assertEquals("New last", updateProfilePage.getLastName());
Assert.assertEquals("new@email.com", updateProfilePage.getEmail());
Assert.assertEquals("test-user@localhost", updateProfilePage.getUsername());
Assert.assertEquals("Username already exists.", updateProfilePage.getError());
events.assertEmpty();
}
@Test
public void updateProfileDuplicatedEmail() {
loginPage.open();
@ -199,7 +278,7 @@ public class RequiredActionUpdateProfileTest {
updateProfilePage.assertCurrent();
updateProfilePage.update("New first", "New last", "keycloak-user@localhost");
updateProfilePage.update("New first", "New last", "keycloak-user@localhost", "test-user@localhost");
updateProfilePage.assertCurrent();

View file

@ -17,7 +17,6 @@
*/
package org.keycloak.testsuite.broker;
import org.codehaus.jackson.map.ObjectMapper;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;

View file

@ -0,0 +1,51 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2012, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.keycloak.testsuite.pages;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
public class LoginUpdateProfileEditUsernameAllowedPage extends LoginUpdateProfilePage {
@FindBy(id = "username")
private WebElement usernameInput;
public void update(String firstName, String lastName, String email, String username) {
usernameInput.clear();
usernameInput.sendKeys(username);
update(firstName, lastName, email);
}
public String getUsername() {
return usernameInput.getAttribute("value");
}
public boolean isCurrent() {
return driver.getTitle().equals("Update Account Information");
}
@Override
public void open() {
throw new UnsupportedOperationException();
}
}

View file

@ -5,6 +5,7 @@
"sslRequired": "external",
"registrationAllowed": true,
"resetPasswordAllowed": true,
"editUsernameAllowed" : true,
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"requiredCredentials": [ "password" ],
@ -32,7 +33,23 @@
}
},
{
"username" : "keycloak-user@localhost",
"username" : "john-doh@localhost",
"enabled": true,
"email" : "john-doh@localhost",
"firstName": "John",
"lastName": "Doh",
"credentials" : [
{ "type" : "password",
"value" : "password" }
],
"realmRoles": ["user"],
"clientRoles": {
"test-app": [ "customer-user" ],
"account": [ "view-profile", "manage-account" ]
}
},
{
"username" : "keycloak-user@localhost",
"enabled": true,
"email" : "keycloak-user@localhost",
"credentials" : [