+
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 43eaa30eb9..b04d387a3a 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -59,6 +59,10 @@ public interface RealmModel extends RoleContainerModel {
void setRememberMe(boolean rememberMe);
+ boolean isEditUsernameAllowed();
+
+ void setEditUsernameAllowed(boolean editUsernameAllowed);
+
//--- brute force settings
boolean isBruteForceProtected();
void setBruteForceProtected(boolean value);
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 7c393bbb33..46e9fa34f9 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
@@ -20,6 +20,7 @@ public class RealmEntity extends AbstractIdentifiableEntity {
private boolean passwordCredentialGrantAllowed;
private boolean resetPasswordAllowed;
private String passwordPolicy;
+ private boolean editUsernameAllowed;
//--- brute force settings
private boolean bruteForceProtected;
private int maxFailureWaitSeconds;
@@ -150,6 +151,14 @@ public class RealmEntity extends AbstractIdentifiableEntity {
this.resetPasswordAllowed = resetPasswordAllowed;
}
+ public boolean isEditUsernameAllowed() {
+ return editUsernameAllowed;
+ }
+
+ public void setEditUsernameAllowed(boolean editUsernameAllowed) {
+ this.editUsernameAllowed = editUsernameAllowed;
+ }
+
public String getPasswordPolicy() {
return passwordPolicy;
}
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 e9e4b3650b..38b125df3d 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
@@ -124,6 +124,7 @@ public class ModelToRepresentation {
rep.setVerifyEmail(realm.isVerifyEmail());
rep.setResetPasswordAllowed(realm.isResetPasswordAllowed());
+ rep.setEditUsernameAllowed(realm.isEditUsernameAllowed());
rep.setAccessTokenLifespan(realm.getAccessTokenLifespan());
rep.setSsoSessionIdleTimeout(realm.getSsoSessionIdleTimeout());
rep.setSsoSessionMaxLifespan(realm.getSsoSessionMaxLifespan());
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 e2a0573fd4..bfbfda5f5c 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
@@ -103,6 +103,7 @@ public class RepresentationToModel {
if (rep.isRememberMe() != null) newRealm.setRememberMe(rep.isRememberMe());
if (rep.isVerifyEmail() != null) newRealm.setVerifyEmail(rep.isVerifyEmail());
if (rep.isResetPasswordAllowed() != null) newRealm.setResetPasswordAllowed(rep.isResetPasswordAllowed());
+ if (rep.isEditUsernameAllowed() != null) newRealm.setEditUsernameAllowed(rep.isEditUsernameAllowed());
if (rep.getPrivateKey() == null || rep.getPublicKey() == null) {
KeycloakModelUtils.generateRealmKeys(newRealm);
} else {
@@ -426,6 +427,7 @@ public class RepresentationToModel {
if (rep.isRememberMe() != null) realm.setRememberMe(rep.isRememberMe());
if (rep.isVerifyEmail() != null) realm.setVerifyEmail(rep.isVerifyEmail());
if (rep.isResetPasswordAllowed() != null) realm.setResetPasswordAllowed(rep.isResetPasswordAllowed());
+ if (rep.isEditUsernameAllowed() != null) realm.setEditUsernameAllowed(rep.isEditUsernameAllowed());
if (rep.getSslRequired() != null) realm.setSslRequired(SslRequired.valueOf(rep.getSslRequired().toUpperCase()));
if (rep.getAccessCodeLifespan() != null) realm.setAccessCodeLifespan(rep.getAccessCodeLifespan());
if (rep.getAccessCodeLifespanUserAction() != null) realm.setAccessCodeLifespanUserAction(rep.getAccessCodeLifespanUserAction());
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 26e09bb53e..184b965e88 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
@@ -270,6 +270,16 @@ public class RealmAdapter implements RealmModel {
realm.setResetPasswordAllowed(resetPassword);
}
+ @Override
+ public boolean isEditUsernameAllowed() {
+ return realm.isEditUsernameAllowed();
+ }
+
+ @Override
+ public void setEditUsernameAllowed(boolean editUsernameAllowed) {
+ realm.setEditUsernameAllowed(editUsernameAllowed);
+ }
+
@Override
public PasswordPolicy getPasswordPolicy() {
if (passwordPolicy == null) {
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 afb1c2c574..dc5334f3fc 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
@@ -8,14 +8,12 @@ import org.keycloak.models.AuthenticatorModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderModel;
-import org.keycloak.models.LDAPConstants;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProviderModel;
-import org.keycloak.models.UserModel;
import org.keycloak.models.cache.entities.CachedRealm;
import org.keycloak.models.utils.KeycloakModelUtils;
@@ -256,6 +254,18 @@ public class RealmAdapter implements RealmModel {
updated.setResetPasswordAllowed(resetPasswordAllowed);
}
+ @Override
+ public boolean isEditUsernameAllowed() {
+ if (updated != null) return updated.isEditUsernameAllowed();
+ return cached.isEditUsernameAllowed();
+ }
+
+ @Override
+ public void setEditUsernameAllowed(boolean editUsernameAllowed) {
+ getDelegateForUpdate();
+ updated.setEditUsernameAllowed(editUsernameAllowed);
+ }
+
@Override
public int getSsoSessionIdleTimeout() {
if (updated != null) return updated.getSsoSessionIdleTimeout();
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 d93acf6948..115fe177e7 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
@@ -42,6 +42,7 @@ public class CachedRealm {
private boolean passwordCredentialGrantAllowed;
private boolean resetPasswordAllowed;
private boolean identityFederationEnabled;
+ private boolean editUsernameAllowed;
//--- brute force settings
private boolean bruteForceProtected;
private int maxFailureWaitSeconds;
@@ -114,6 +115,7 @@ public class CachedRealm {
passwordCredentialGrantAllowed = model.isPasswordCredentialGrantAllowed();
resetPasswordAllowed = model.isResetPasswordAllowed();
identityFederationEnabled = model.isIdentityFederationEnabled();
+ editUsernameAllowed = model.isEditUsernameAllowed();
//--- brute force settings
bruteForceProtected = model.isBruteForceProtected();
maxFailureWaitSeconds = model.getMaxFailureWaitSeconds();
@@ -288,6 +290,10 @@ public class CachedRealm {
return resetPasswordAllowed;
}
+ public boolean isEditUsernameAllowed() {
+ return editUsernameAllowed;
+ }
+
public int getSsoSessionIdleTimeout() {
return ssoSessionIdleTimeout;
}
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 2350a7a617..3f2a00d492 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
@@ -320,6 +320,17 @@ public class RealmAdapter implements RealmModel {
em.flush();
}
+ @Override
+ public boolean isEditUsernameAllowed() {
+ return realm.isEditUsernameAllowed();
+ }
+
+ @Override
+ public void setEditUsernameAllowed(boolean editUsernameAllowed) {
+ realm.setEditUsernameAllowed(editUsernameAllowed);
+ em.flush();
+ }
+
@Override
public int getNotBefore() {
return realm.getNotBefore();
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 089cd67039..8f1958b304 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
@@ -59,6 +59,8 @@ public class RealmEntity {
protected boolean rememberMe;
@Column(name="PASSWORD_POLICY")
protected String passwordPolicy;
+ @Column(name="EDIT_USERNAME_ALLOWED")
+ protected boolean editUsernameAllowed;
@Column(name="SSO_IDLE_TIMEOUT")
private int ssoSessionIdleTimeout;
@@ -254,6 +256,14 @@ public class RealmEntity {
this.resetPasswordAllowed = resetPasswordAllowed;
}
+ public boolean isEditUsernameAllowed() {
+ return editUsernameAllowed;
+ }
+
+ public void setEditUsernameAllowed(boolean editUsernameAllowed) {
+ this.editUsernameAllowed = editUsernameAllowed;
+ }
+
public int getSsoSessionIdleTimeout() {
return ssoSessionIdleTimeout;
}
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 e6674772f6..673477d337 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
@@ -254,6 +254,17 @@ public class RealmAdapter extends AbstractMongoAdapter impleme
updateRealm();
}
+ @Override
+ public boolean isEditUsernameAllowed() {
+ return realm.isEditUsernameAllowed();
+ }
+
+ @Override
+ public void setEditUsernameAllowed(boolean editUsernameAllowed) {
+ realm.setEditUsernameAllowed(editUsernameAllowed);
+ updateRealm();
+ }
+
@Override
public PasswordPolicy getPasswordPolicy() {
if (passwordPolicy == null) {
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 026c877637..39f62991d5 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -35,23 +35,35 @@ import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventStoreProvider;
import org.keycloak.events.EventType;
import org.keycloak.login.LoginFormsProvider;
-import org.keycloak.models.*;
+import org.keycloak.models.AccountRoles;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.Constants;
+import org.keycloak.models.FederatedIdentityModel;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.ModelReadOnlyException;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
-import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.protocol.oidc.utils.RedirectUtils;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.ForbiddenException;
+import org.keycloak.services.Urls;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.Auth;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.messages.Messages;
-import org.keycloak.services.Urls;
import org.keycloak.services.util.CookieHelper;
import org.keycloak.services.util.ResolveRelative;
import org.keycloak.services.validation.Validation;
@@ -73,7 +85,6 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.core.Variant;
-
import java.lang.reflect.Method;
import java.net.URI;
import java.util.HashSet;
@@ -414,13 +425,16 @@ public class AccountService {
UserModel user = auth.getUser();
- List errors = Validation.validateUpdateProfileForm(formData);
+ List errors = Validation.validateUpdateProfileForm(realm, formData);
if (errors != null && !errors.isEmpty()) {
setReferrerOnPage();
return account.setErrors(errors).setProfileFormData(formData).createResponse(AccountPages.ACCOUNT);
}
try {
+ if (realm.isEditUsernameAllowed()) {
+ user.setUsername(formData.getFirst("username"));
+ }
user.setFirstName(formData.getFirst("firstName"));
user.setLastName(formData.getFirst("lastName"));
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index d6dc0d36b8..55d97b0418 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -186,6 +186,9 @@ public class UsersResource {
}
private void updateUserFromRep(UserModel user, UserRepresentation rep, Set attrsToRemove) {
+ if (realm.isEditUsernameAllowed()) {
+ user.setUsername(rep.getUsername());
+ }
user.setEmail(rep.getEmail());
user.setFirstName(rep.getFirstName());
user.setLastName(rep.getLastName());
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 1a4392b2a0..fefe74ef8d 100755
--- a/services/src/main/java/org/keycloak/services/validation/Validation.java
+++ b/services/src/main/java/org/keycloak/services/validation/Validation.java
@@ -1,17 +1,16 @@
package org.keycloak.services.validation;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Pattern;
-
-import javax.ws.rs.core.MultivaluedMap;
-
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.messages.Messages;
+import javax.ws.rs.core.MultivaluedMap;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
public class Validation {
public static final String FIELD_PASSWORD_CONFIRM = "password-confirm";
@@ -66,10 +65,17 @@ public class Validation {
errors.add(new FormMessage(field, message));
}
-
public static List validateUpdateProfileForm(MultivaluedMap formData) {
+ return validateUpdateProfileForm(null, formData);
+ }
+
+ public static List validateUpdateProfileForm(RealmModel realm, MultivaluedMap formData) {
List errors = new ArrayList<>();
+ if (realm != null && realm.isEditUsernameAllowed() && isEmpty(formData.getFirst(FIELD_USERNAME))) {
+ addError(errors, FIELD_USERNAME, Messages.MISSING_USERNAME);
+ }
+
if (isEmpty(formData.getFirst(FIELD_FIRST_NAME))) {
addError(errors, FIELD_FIRST_NAME, Messages.MISSING_FIRST_NAME);
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index b8cf2a8dba..0f1eba124e 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -24,11 +24,9 @@ package org.keycloak.testsuite.account;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
-import org.keycloak.account.freemarker.model.ApplicationsBean;
import org.keycloak.events.Details;
import org.keycloak.events.Event;
import org.keycloak.events.EventType;
@@ -155,6 +153,9 @@ public class AccountTest {
@Override
public void config(RealmManager manager, RealmModel defaultRealm, RealmModel appRealm) {
UserModel user = manager.getSession().users().getUserByUsername("test-user@localhost", appRealm);
+ user.setFirstName("Tom");
+ user.setLastName("Brady");
+ user.setEmail("test-user@localhost");
UserCredentialModel cred = new UserCredentialModel();
cred.setType(CredentialRepresentation.PASSWORD);
@@ -393,6 +394,61 @@ public class AccountTest {
events.expectAccount(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
}
+ @Test
+ public void changeUsername() {
+ // allow to edit the username in realm
+ keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ appRealm.setEditUsernameAllowed(true);
+ }
+ });
+
+ try {
+ profilePage.open();
+ loginPage.login("test-user@localhost", "password");
+
+ events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT).assertEvent();
+
+ Assert.assertEquals("test-user@localhost", profilePage.getUsername());
+ Assert.assertEquals("Tom", profilePage.getFirstName());
+ Assert.assertEquals("Brady", profilePage.getLastName());
+ Assert.assertEquals("test-user@localhost", profilePage.getEmail());
+
+ // All fields are required, so there should be an error when something is missing.
+ profilePage.updateProfile("", "New first", "New last", "new@email.com");
+
+ Assert.assertEquals("Please specify username.", profilePage.getError());
+ Assert.assertEquals("", profilePage.getUsername());
+ Assert.assertEquals("New first", profilePage.getFirstName());
+ Assert.assertEquals("New last", profilePage.getLastName());
+ Assert.assertEquals("new@email.com", profilePage.getEmail());
+
+ events.assertEmpty();
+
+ profilePage.updateProfile("test-user-new@localhost", "New first", "New last", "new@email.com");
+
+ Assert.assertEquals("Your account has been updated.", profilePage.getSuccess());
+ Assert.assertEquals("test-user-new@localhost", profilePage.getUsername());
+ Assert.assertEquals("New first", profilePage.getFirstName());
+ Assert.assertEquals("New last", profilePage.getLastName());
+ Assert.assertEquals("new@email.com", profilePage.getEmail());
+
+ } finally {
+ // reset user for other tests
+ profilePage.updateProfile("test-user@localhost", "Tom", "Brady", "test-user@localhost");
+ events.clear();
+
+ // reset realm
+ keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ appRealm.setEditUsernameAllowed(false);
+ }
+ });
+ }
+ }
+
@Test
public void setupTotp() {
totpPage.open();
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 afee212b59..df62362337 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
@@ -242,6 +242,7 @@ public class AdminAPITest {
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());
+ if (rep.isEditUsernameAllowed() != null) Assert.assertEquals(rep.isEditUsernameAllowed(), storedRealm.isEditUsernameAllowed());
if (rep.getSslRequired() != null) Assert.assertEquals(rep.getSslRequired(), storedRealm.getSslRequired());
if (rep.getAccessCodeLifespan() != null) Assert.assertEquals(rep.getAccessCodeLifespan(), storedRealm.getAccessCodeLifespan());
if (rep.getAccessCodeLifespanUserAction() != null)
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 1b02d0b122..6cf642728a 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
@@ -71,6 +71,7 @@ public class RealmTest extends AbstractClientTest {
rep.setAccessCodeLifespanLogin(1234);
rep.setRegistrationAllowed(true);
rep.setRegistrationEmailAsUsername(true);
+ rep.setEditUsernameAllowed(true);
realm.update(rep);
@@ -81,16 +82,19 @@ public class RealmTest extends AbstractClientTest {
assertEquals(1234, rep.getAccessCodeLifespanLogin().intValue());
assertEquals(Boolean.TRUE, rep.isRegistrationAllowed());
assertEquals(Boolean.TRUE, rep.isRegistrationEmailAsUsername());
+ assertEquals(Boolean.TRUE, rep.isEditUsernameAllowed());
// second change
rep.setRegistrationAllowed(false);
rep.setRegistrationEmailAsUsername(false);
+ rep.setEditUsernameAllowed(false);
realm.update(rep);
rep = realm.toRepresentation();
assertEquals(Boolean.FALSE, rep.isRegistrationAllowed());
assertEquals(Boolean.FALSE, rep.isRegistrationEmailAsUsername());
+ assertEquals(Boolean.FALSE, rep.isEditUsernameAllowed());
}
@Test
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java
index 5b3623df33..42dd464011 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java
@@ -1,13 +1,13 @@
package org.keycloak.testsuite.admin;
import org.junit.Assert;
-import org.junit.Ignore;
import org.junit.Test;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.representations.idm.ErrorRepresentation;
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import javax.ws.rs.ClientErrorException;
@@ -410,4 +410,78 @@ public class UserTest extends AbstractClientTest {
Assert.assertEquals("invalidClientId not enabled", error.getErrorMessage());
}
}
+
+ @Test
+ public void updateUserWithNewUsername() {
+ switchEditUsernameAllowedOn();
+ String id = createUser();
+
+ UserResource user = realm.users().get(id);
+ UserRepresentation userRep = user.toRepresentation();
+ userRep.setUsername("user11");
+ user.update(userRep);
+
+ userRep = realm.users().get(id).toRepresentation();
+ assertEquals("user11", userRep.getUsername());
+ }
+
+ @Test
+ public void updateUserWithNewUsernameNotPossible() {
+ String id = createUser();
+
+ UserResource user = realm.users().get(id);
+ UserRepresentation userRep = user.toRepresentation();
+ userRep.setUsername("user11");
+ user.update(userRep);
+
+ userRep = realm.users().get(id).toRepresentation();
+ assertEquals("user1", userRep.getUsername());
+ }
+
+ @Test
+ public void updateUserWithNewUsernameAccessingViaOldUsername() {
+ switchEditUsernameAllowedOn();
+ createUser();
+
+ try {
+ UserResource user = realm.users().get("user1");
+ UserRepresentation userRep = user.toRepresentation();
+ userRep.setUsername("user1");
+ user.update(userRep);
+
+ realm.users().get("user11").toRepresentation();
+ fail("Expected failure");
+ } catch (ClientErrorException e) {
+ assertEquals(404, e.getResponse().getStatus());
+ }
+ }
+
+ @Test
+ public void updateUserWithExistingUsername() {
+ switchEditUsernameAllowedOn();
+ createUser();
+
+ UserRepresentation userRep = new UserRepresentation();
+ userRep.setUsername("user2");
+ Response response = realm.users().create(userRep);
+ String createdId = ApiUtil.getCreatedId(response);
+ response.close();
+
+ try {
+ UserResource user = realm.users().get(createdId);
+ userRep = user.toRepresentation();
+ userRep.setUsername("user1");
+ user.update(userRep);
+ fail("Expected failure");
+ } catch (ClientErrorException e) {
+ assertEquals(409, e.getResponse().getStatus());
+ }
+ }
+
+ private void switchEditUsernameAllowedOn() {
+ RealmRepresentation rep = realm.toRepresentation();
+ rep.setEditUsernameAllowed(true);
+ realm.update(rep);
+ }
+
}
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 9a4fb5f113..7637f1d00f 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
@@ -21,6 +21,7 @@ public class ModelTest extends AbstractModelTest {
realm.setRegistrationAllowed(true);
realm.setRegistrationEmailAsUsername(true);
realm.setResetPasswordAllowed(true);
+ realm.setEditUsernameAllowed(true);
realm.setSslRequired(SslRequired.EXTERNAL);
realm.setVerifyEmail(true);
realm.setAccessTokenLifespan(1000);
@@ -55,6 +56,7 @@ public class ModelTest extends AbstractModelTest {
Assert.assertEquals(expected.isRegistrationAllowed(), actual.isRegistrationAllowed());
Assert.assertEquals(expected.isRegistrationEmailAsUsername(), actual.isRegistrationEmailAsUsername());
Assert.assertEquals(expected.isResetPasswordAllowed(), actual.isResetPasswordAllowed());
+ Assert.assertEquals(expected.isEditUsernameAllowed(), actual.isEditUsernameAllowed());
Assert.assertEquals(expected.getSslRequired(), actual.getSslRequired());
Assert.assertEquals(expected.isVerifyEmail(), actual.isVerifyEmail());
Assert.assertEquals(expected.getAccessTokenLifespan(), actual.getAccessTokenLifespan());
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
index 1aa7b219c3..18bc795701 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
@@ -35,6 +35,9 @@ public class AccountUpdateProfilePage extends AbstractAccountPage {
public static String PATH = RealmsResource.accountUrl(UriBuilder.fromUri(Constants.AUTH_SERVER_ROOT)).build("test").toString();
+ @FindBy(id = "username")
+ private WebElement usernameInput;
+
@FindBy(id = "firstName")
private WebElement firstNameInput;
@@ -74,11 +77,28 @@ public class AccountUpdateProfilePage extends AbstractAccountPage {
submitButton.click();
}
+ public void updateProfile(String username, String firstName, String lastName, String email) {
+ usernameInput.clear();
+ usernameInput.sendKeys(username);
+ firstNameInput.clear();
+ firstNameInput.sendKeys(firstName);
+ lastNameInput.clear();
+ lastNameInput.sendKeys(lastName);
+ emailInput.clear();
+ emailInput.sendKeys(email);
+
+ submitButton.click();
+ }
+
public void clickCancel() {
cancelButton.click();
}
+ public String getUsername() {
+ return usernameInput.getAttribute("value");
+ }
+
public String getFirstName() {
return firstNameInput.getAttribute("value");
}