diff --git a/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java b/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java index 0bb7bce7c9..6dcd3955ae 100755 --- a/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java +++ b/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java @@ -214,12 +214,26 @@ public class AccountRestService { user.setLastName(userRep.getLastName()); if (userRep.getAttributes() != null) { - for (String k : user.getAttributes().keySet()) { + Set attributeKeys = new HashSet<>(user.getAttributes().keySet()); + // We store username and other attributes as attributes (for future UserProfile) + // but don't propagate them to the UserRepresentation, so userRep will never contain them + // if the user did not explicitly add them + attributeKeys.remove(UserModel.FIRST_NAME); + attributeKeys.remove(UserModel.LAST_NAME); + attributeKeys.remove(UserModel.EMAIL); + attributeKeys.remove(UserModel.USERNAME); + for (String k : attributeKeys) { if (!userRep.getAttributes().containsKey(k)) { user.removeAttribute(k); } } + Map> attributes = userRep.getAttributes(); + // Make sure we don't accidentally update any of the fields through attributes + attributes.remove(UserModel.FIRST_NAME); + attributes.remove(UserModel.LAST_NAME); + attributes.remove(UserModel.EMAIL); + attributes.remove(UserModel.USERNAME); for (Map.Entry> e : userRep.getAttributes().entrySet()) { user.setAttribute(e.getKey(), e.getValue()); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java index 1656cb7fa0..33cf4e6cb1 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java @@ -180,6 +180,32 @@ public class AccountRestServiceTest extends AbstractRestServiceTest { } + @Test + public void testUpdateProfileCannotChangeThroughAttributes() throws IOException { + UserRepresentation user = SimpleHttp.doGet(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).asJson(UserRepresentation.class); + String originalUsername = user.getUsername(); + Map> originalAttributes = new HashMap<>(user.getAttributes()); + + try { + user.getAttributes().put("username", Collections.singletonList("Username")); + user.getAttributes().put("attr2", Collections.singletonList("val2")); + + user = updateAndGet(user); + + assertEquals(user.getUsername(), originalUsername); + } finally { + RealmRepresentation realmRep = adminClient.realm("test").toRepresentation(); + realmRep.setEditUsernameAllowed(true); + adminClient.realm("test").update(realmRep); + + user.setUsername(originalUsername); + user.setAttributes(originalAttributes); + SimpleHttp.Response response = SimpleHttp.doPost(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).json(user).asResponse(); + System.out.println(response.asString()); + assertEquals(204, response.getStatus()); + } + } + // KEYCLOAK-7572 @Test public void testUpdateProfileWithRegistrationEmailAsUsername() throws IOException {