keycloak-memoizeit

Merge pull request #3895 from stianst/KEYCLOAK-943 KEYCLOAK-943

2/24/2017 11:17:46 AM

Details

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 9b36a45..a3c3fcb 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -60,12 +60,7 @@ import org.keycloak.services.validation.Validation;
 import org.keycloak.storage.ReadOnlyException;
 import org.keycloak.util.JsonSerialization;
 
-import javax.ws.rs.Consumes;
-import javax.ws.rs.GET;
-import javax.ws.rs.OPTIONS;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.QueryParam;
+import javax.ws.rs.*;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
@@ -259,27 +254,33 @@ public class AccountService extends AbstractSecuredLocalService {
      */
     @Path("/")
     @GET
+    @Produces(MediaType.TEXT_HTML)
     public Response accountPage() {
-        List<MediaType> types = headers.getAcceptableMediaTypes();
-        if (types.contains(MediaType.WILDCARD_TYPE) || (types.contains(MediaType.TEXT_HTML_TYPE))) {
-            return forwardToPage(null, AccountPages.ACCOUNT);
-        } else if (types.contains(MediaType.APPLICATION_JSON_TYPE)) {
-            requireOneOf(AccountRoles.MANAGE_ACCOUNT, AccountRoles.VIEW_PROFILE);
-
-            UserRepresentation rep = ModelToRepresentation.toRepresentation(session, realm, auth.getUser());
-            if (rep.getAttributes() != null) {
-                Iterator<String> itr = rep.getAttributes().keySet().iterator();
-                while (itr.hasNext()) {
-                    if (itr.next().startsWith("keycloak.")) {
-                        itr.remove();
-                    }
+        return forwardToPage(null, AccountPages.ACCOUNT);
+    }
+
+    /**
+     * Get account information.
+     *
+     * @return
+     */
+    @Path("/")
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response accountPageJson() {
+        requireOneOf(AccountRoles.MANAGE_ACCOUNT, AccountRoles.VIEW_PROFILE);
+
+        UserRepresentation rep = ModelToRepresentation.toRepresentation(session, realm, auth.getUser());
+        if (rep.getAttributes() != null) {
+            Iterator<String> itr = rep.getAttributes().keySet().iterator();
+            while (itr.hasNext()) {
+                if (itr.next().startsWith("keycloak.")) {
+                    itr.remove();
                 }
             }
-
-            return Cors.add(request, Response.ok(rep)).auth().allowedOrigins(auth.getToken()).build();
-        } else {
-            return Response.notAcceptable(Variant.VariantListBuilder.newInstance().mediaTypes(MediaType.TEXT_HTML_TYPE, MediaType.APPLICATION_JSON_TYPE).build()).build();
         }
+
+        return Cors.add(request, Response.ok(rep)).auth().allowedOrigins(auth.getToken()).build();
     }
 
     public static UriBuilder totpUrl(UriBuilder base) {
@@ -377,6 +378,8 @@ public class AccountService extends AbstractSecuredLocalService {
 
         UserModel user = auth.getUser();
 
+        event.event(EventType.UPDATE_PROFILE).client(auth.getClient()).user(auth.getUser());
+
         List<FormMessage> errors = Validation.validateUpdateProfileForm(realm.isEditUsernameAllowed(), formData);
         if (errors != null && !errors.isEmpty()) {
             setReferrerOnPage();
@@ -384,49 +387,15 @@ public class AccountService extends AbstractSecuredLocalService {
         }
 
         try {
-            if (realm.isEditUsernameAllowed()) {
-                String username = formData.getFirst("username");
-
-                UserModel existing = session.users().getUserByUsername(username, realm);
-                if (existing != null && !existing.getId().equals(user.getId())) {
-                    throw new ModelDuplicateException(Messages.USERNAME_EXISTS);
-                }
+            updateUsername(formData.getFirst("username"), user);
+            updateEmail(formData.getFirst("email"), user);
 
-                user.setUsername(username);
-            }
             user.setFirstName(formData.getFirst("firstName"));
             user.setLastName(formData.getFirst("lastName"));
 
-            String email = formData.getFirst("email");
-            String oldEmail = user.getEmail();
-            boolean emailChanged = oldEmail != null ? !oldEmail.equals(email) : email != null;
-            if (emailChanged && !realm.isDuplicateEmailsAllowed()) {
-                UserModel existing = session.users().getUserByEmail(email, realm);
-                if (existing != null && !existing.getId().equals(user.getId())) {
-                    throw new ModelDuplicateException(Messages.EMAIL_EXISTS);
-                }
-            }
-
-            user.setEmail(email);
-
             AttributeFormDataProcessor.process(formData, realm, user);
 
-            event.event(EventType.UPDATE_PROFILE).client(auth.getClient()).user(auth.getUser()).success();
-
-            if (emailChanged) {
-                user.setEmailVerified(false);
-                event.clone().event(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, email).success();
-            }
-
-            if (realm.isRegistrationEmailAsUsername()) {
-                if (!realm.isDuplicateEmailsAllowed()) {
-                    UserModel existing = session.users().getUserByEmail(email, realm);
-                    if (existing != null && !existing.getId().equals(user.getId())) {
-                        throw new ModelDuplicateException(Messages.USERNAME_EXISTS);
-                    }
-                }
-                user.setUsername(email);
-            }
+            event.success();
 
             setReferrerOnPage();
             return account.setSuccess(Messages.ACCOUNT_UPDATED).createResponse(AccountPages.ACCOUNT);
@@ -439,6 +408,82 @@ public class AccountService extends AbstractSecuredLocalService {
         }
     }
 
+    @Path("/")
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response processAccountUpdateJson(UserRepresentation userRep) {
+        require(AccountRoles.MANAGE_ACCOUNT);
+        if (auth.isCookieAuthenticated()) {
+            throw new ForbiddenException();
+        }
+
+        UserModel user = auth.getUser();
+
+        event.event(EventType.UPDATE_PROFILE).client(auth.getClient()).user(auth.getUser());
+
+        updateUsername(userRep.getUsername(), user);
+        updateEmail(userRep.getEmail(), user);
+
+        user.setFirstName(userRep.getFirstName());
+        user.setLastName(userRep.getLastName());
+
+        if (userRep.getAttributes() != null) {
+            for (String k : user.getAttributes().keySet()) {
+                if (!userRep.getAttributes().containsKey(k)) {
+                    user.removeAttribute(k);
+                }
+            }
+
+            for (Map.Entry<String, List<String>> e : userRep.getAttributes().entrySet()) {
+                user.setAttribute(e.getKey(), e.getValue());
+            }
+        }
+
+        event.success();
+
+        return Cors.add(request, Response.ok()).build();
+    }
+
+    private void updateUsername(String username, UserModel user) {
+        if (realm.isEditUsernameAllowed() && username != null) {
+            UserModel existing = session.users().getUserByUsername(username, realm);
+            if (existing != null && !existing.getId().equals(user.getId())) {
+                throw new ModelDuplicateException(Messages.USERNAME_EXISTS);
+            }
+
+            user.setUsername(username);
+        }
+    }
+
+    private void updateEmail(String email, UserModel user) {
+        String oldEmail = user.getEmail();
+        boolean emailChanged = oldEmail != null ? !oldEmail.equals(email) : email != null;
+        if (emailChanged && !realm.isDuplicateEmailsAllowed()) {
+            UserModel existing = session.users().getUserByEmail(email, realm);
+            if (existing != null && !existing.getId().equals(user.getId())) {
+                throw new ModelDuplicateException(Messages.EMAIL_EXISTS);
+            }
+        }
+
+        user.setEmail(email);
+
+        if (emailChanged) {
+            user.setEmailVerified(false);
+            event.clone().event(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, email).success();
+        }
+
+        if (realm.isRegistrationEmailAsUsername()) {
+            if (!realm.isDuplicateEmailsAllowed()) {
+                UserModel existing = session.users().getUserByEmail(email, realm);
+                if (existing != null && !existing.getId().equals(user.getId())) {
+                    throw new ModelDuplicateException(Messages.USERNAME_EXISTS);
+                }
+            }
+            user.setUsername(email);
+        }
+    }
+
     @Path("totp-remove")
     @GET
     public Response processTotpRemove(@QueryParam("stateChecker") String stateChecker) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index 3f76556..db5c4ed 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -525,8 +525,8 @@ public class AccountTest extends AbstractTestRealmKeycloakTest {
         Assert.assertEquals("New last", profilePage.getLastName());
         Assert.assertEquals("new@email.com", profilePage.getEmail());
 
-        events.expectAccount(EventType.UPDATE_PROFILE).assertEvent();
         events.expectAccount(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
+        events.expectAccount(EventType.UPDATE_PROFILE).assertEvent();
 
         // reset user for other tests
         profilePage.updateProfile("Tom", "Brady", "test-user@localhost");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ProfileTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ProfileTest.java
index e33447a..63a2315 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ProfileTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ProfileTest.java
@@ -22,6 +22,8 @@ import org.apache.http.HttpHeaders;
 import org.apache.http.HttpResponse;
 import org.apache.http.client.HttpClient;
 import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
 import org.apache.http.impl.client.DefaultHttpClient;
 import org.jboss.arquillian.drone.api.annotation.Default;
 import org.jboss.arquillian.graphene.context.GrapheneContext;
@@ -45,12 +47,14 @@ import org.keycloak.testsuite.pages.AccountApplicationsPage;
 import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
 import org.keycloak.testsuite.pages.LoginPage;
 import org.keycloak.testsuite.pages.OAuthGrantPage;
+import org.keycloak.testsuite.runonserver.SerializationUtil;
 import org.keycloak.testsuite.util.ClientBuilder;
 import org.keycloak.testsuite.util.OAuthClient;
 import org.keycloak.testsuite.util.RealmBuilder;
 import org.keycloak.testsuite.util.RealmRepUtil;
 import org.keycloak.testsuite.util.UserBuilder;
 import org.keycloak.testsuite.util.WaitUtils;
+import org.keycloak.util.JsonSerialization;
 import org.openqa.selenium.By;
 import org.openqa.selenium.Capabilities;
 import org.openqa.selenium.JavascriptExecutor;
@@ -68,6 +72,7 @@ import java.io.IOException;
 import java.net.URI;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
@@ -146,23 +151,49 @@ public class ProfileTest extends AbstractTestRealmKeycloakTest {
 
         HttpResponse response = doGetProfile(token, null);
         assertEquals(200, response.getStatusLine().getStatusCode());
-        JSONObject profile = new JSONObject(IOUtils.toString(response.getEntity().getContent()));
+        UserRepresentation profile = JsonSerialization.readValue(IOUtils.toString(response.getEntity().getContent()), UserRepresentation.class);
 
-        assertEquals("test-user@localhost", profile.getString("username"));
-        assertEquals("test-user@localhost", profile.getString("email"));
-        assertEquals("First", profile.getString("firstName"));
-        assertEquals("Last", profile.getString("lastName"));
+        assertEquals("test-user@localhost", profile.getUsername());
+        assertEquals("test-user@localhost", profile.getEmail());
+        assertEquals("First", profile.getFirstName());
+        assertEquals("Last", profile.getLastName());
 
-        JSONObject attributes = profile.getJSONObject("attributes");
-        JSONArray attrValue = attributes.getJSONArray("key1");
-        assertEquals(1, attrValue.length());
+        Map<String, List<String>> attributes = profile.getAttributes();
+        List<String> attrValue = attributes.get("key1");
+        assertEquals(1, attrValue.size());
         assertEquals("value1", attrValue.get(0));
-        attrValue = attributes.getJSONArray("key2");
-        assertEquals(1, attrValue.length());
+        attrValue = attributes.get("key2");
+        assertEquals(1, attrValue.size());
         assertEquals("value2", attrValue.get(0));
     }
 
     @Test
+    public void updateProfile() throws Exception {
+        oauth.doLogin("test-user@localhost", "password");
+
+        String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+        String token = oauth.doAccessTokenRequest(code, "password").getAccessToken();
+
+        UserRepresentation user = new UserRepresentation();
+        user.setUsername("test-user@localhost");
+        user.setFirstName("NewFirst");
+        user.setLastName("NewLast");
+        user.setEmail("NewEmail@localhost");
+
+        HttpResponse response = doUpdateProfile(token, null, JsonSerialization.writeValueAsString(user));
+        assertEquals(200, response.getStatusLine().getStatusCode());
+
+        response = doGetProfile(token, null);
+
+        UserRepresentation profile = JsonSerialization.readValue(IOUtils.toString(response.getEntity().getContent()), UserRepresentation.class);
+
+        assertEquals("test-user@localhost", profile.getUsername());
+        assertEquals("newemail@localhost", profile.getEmail());
+        assertEquals("NewFirst", profile.getFirstName());
+        assertEquals("NewLast", profile.getLastName());
+    }
+
+    @Test
     public void getProfileCors() throws Exception {
         oauth.doLogin("test-user@localhost", "password");
 
@@ -274,6 +305,21 @@ public class ProfileTest extends AbstractTestRealmKeycloakTest {
         return client.execute(get);
     }
 
+    private HttpResponse doUpdateProfile(String token, String origin, String value) throws IOException {
+        HttpClient client = new DefaultHttpClient();
+        HttpPost post = new HttpPost(UriBuilder.fromUri(getAccountURI()).build());
+        if (token != null) {
+            post.setHeader(HttpHeaders.AUTHORIZATION, "bearer " + token);
+        }
+        if (origin != null) {
+            post.setHeader("Origin", origin);
+        }
+        post.setHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON);
+        post.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
+        post.setEntity(new StringEntity(value));
+        return client.execute(post);
+    }
+
     private String[] doGetProfileJs(String authServerRoot, String token) {
         UriBuilder uriBuilder = UriBuilder.fromUri(authServerRoot)
                 .path(TestApplicationResource.class)