org.cloudfoundry.identity.uaa.scim.endpoints.ScimUserEndpointsMockMvcTests.java Source code

Java tutorial

Introduction

Here is the source code for org.cloudfoundry.identity.uaa.scim.endpoints.ScimUserEndpointsMockMvcTests.java

Source

/*******************************************************************************
 *     Cloud Foundry
 *     Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved.
 *
 *     This product is licensed to you under the Apache License, Version 2.0 (the "License").
 *     You may not use this product except in compliance with the License.
 *
 *     This product includes a number of subcomponents with
 *     separate copyright notices and license terms. Your use of these
 *     subcomponents is subject to the terms and conditions of the
 *     subcomponent's license, as noted in the LICENSE file.
 *******************************************************************************/
package org.cloudfoundry.identity.uaa.scim.endpoints;

import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.cloudfoundry.identity.uaa.account.UserAccountStatus;
import org.cloudfoundry.identity.uaa.codestore.ExpiringCode;
import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore;
import org.cloudfoundry.identity.uaa.constants.OriginKeys;
import org.cloudfoundry.identity.uaa.invitations.InvitationConstants;
import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest;
import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils;
import org.cloudfoundry.identity.uaa.provider.IdentityProvider;
import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning;
import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition;
import org.cloudfoundry.identity.uaa.resources.SearchResults;
import org.cloudfoundry.identity.uaa.scim.ScimUser;
import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning;
import org.cloudfoundry.identity.uaa.scim.exception.UserAlreadyVerifiedException;
import org.cloudfoundry.identity.uaa.scim.test.JsonObjectMatcherUtils;
import org.cloudfoundry.identity.uaa.util.JsonUtils;
import org.cloudfoundry.identity.uaa.util.SetServerNameRequestPostProcessor;
import org.cloudfoundry.identity.uaa.zone.IdentityZone;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter;
import org.hamcrest.MatcherAssert;
import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import static org.cloudfoundry.identity.uaa.codestore.ExpiringCodeType.REGISTRATION;
import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf;
import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.utils;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.security.oauth2.common.util.OAuth2Utils.CLIENT_ID;
import static org.springframework.security.oauth2.common.util.OAuth2Utils.REDIRECT_URI;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.util.StringUtils.hasText;

public class ScimUserEndpointsMockMvcTests extends InjectedMockContextTest {

    public static final String HTTP_REDIRECT_EXAMPLE_COM = "http://redirect.example.com";
    public static final String USER_PASSWORD = "pas5Word";
    private String scimReadWriteToken;
    private String scimCreateToken;
    private String uaaAdminToken;
    private String uaaAdminTokenInOtherZone;
    private RandomValueStringGenerator generator = new RandomValueStringGenerator();
    private MockMvcUtils mockMvcUtils = utils();
    private ClientDetails clientDetails;
    private ScimUserProvisioning usersRepository;
    private ExpiringCodeStore codeStore;

    @Before
    public void setUp() throws Exception {
        String adminToken = testClient.getClientCredentialsOAuthAccessToken("admin", "adminsecret",
                "clients.read clients.write clients.secret clients.admin uaa.admin");
        String clientId = generator.generate().toLowerCase();
        String clientSecret = generator.generate().toLowerCase();
        String authorities = "scim.read,scim.write,password.write,oauth.approvals,scim.create,uaa.admin";
        clientDetails = utils().createClient(this.getMockMvc(), adminToken, clientId, clientSecret,
                Collections.singleton("oauth"), Arrays.asList("foo", "bar"),
                Collections.singletonList("client_credentials"), authorities);
        scimReadWriteToken = testClient.getClientCredentialsOAuthAccessToken(clientId, clientSecret,
                "scim.read scim.write password.write");
        scimCreateToken = testClient.getClientCredentialsOAuthAccessToken(clientId, clientSecret, "scim.create");
        usersRepository = getWebApplicationContext().getBean(ScimUserProvisioning.class);
        codeStore = getWebApplicationContext().getBean(ExpiringCodeStore.class);
        uaaAdminToken = testClient.getClientCredentialsOAuthAccessToken(clientId, clientSecret, "uaa.admin");
    }

    private ScimUser createUser(String token) throws Exception {
        return createUser(token, null);
    }

    private ScimUser createUser(String token, String subdomain) throws Exception {
        return createUser(getScimUser(), token, subdomain);
    }

    private ScimUser createUser(ScimUser user, String token, String subdomain) throws Exception {
        return createUser(user, token, subdomain, null);
    }

    private ScimUser createUser(ScimUser user, String token, String subdomain, String switchZone) throws Exception {
        String password = hasText(user.getPassword()) ? user.getPassword() : "pas5word";
        user.setPassword(password);
        MvcResult result = createUserAndReturnResult(user, token, subdomain, switchZone)
                .andExpect(status().isCreated()).andExpect(header().string("ETag", "\"0\""))
                .andExpect(jsonPath("$.userName").value(user.getUserName()))
                .andExpect(jsonPath("$.emails[0].value").value(user.getUserName()))
                .andExpect(jsonPath("$.name.familyName").value(user.getFamilyName()))
                .andExpect(jsonPath("$.name.givenName").value(user.getGivenName())).andReturn();
        user = JsonUtils.readValue(result.getResponse().getContentAsString(), ScimUser.class);
        user.setPassword(password);
        return user;
    }

    private ResultActions createUserAndReturnResult(ScimUser user, String token, String subdomain,
            String switchZone) throws Exception {
        byte[] requestBody = JsonUtils.writeValueAsBytes(user);
        MockHttpServletRequestBuilder post = post("/Users").header("Authorization", "Bearer " + token)
                .contentType(APPLICATION_JSON).content(requestBody);
        if (subdomain != null && !subdomain.equals(""))
            post.with(new SetServerNameRequestPostProcessor(subdomain + ".localhost"));
        if (switchZone != null)
            post.header(IdentityZoneSwitchingFilter.HEADER, switchZone);

        return getMockMvc().perform(post);
    }

    private ScimUser getScimUser() {
        String email = "joe@" + generator.generate().toLowerCase() + ".com";
        ScimUser user = new ScimUser();
        user.setUserName(email);
        user.setName(new ScimUser.Name("Joe", "User"));
        user.addEmail(email);
        return user;
    }

    @Test
    public void testCanCreateUserWithExclamationMark() throws Exception {
        String email = "joe!!@" + generator.generate().toLowerCase() + ".com";
        ScimUser user = getScimUser();
        user.getEmails().clear();
        user.setUserName(email);
        user.setPrimaryEmail(email);
        createUser(user, scimReadWriteToken, null);
    }

    @Test
    public void test_Create_User_Too_Long_Password() throws Exception {
        String email = "joe@" + generator.generate().toLowerCase() + ".com";
        ScimUser user = getScimUser();
        user.setUserName(email);
        user.setPrimaryEmail(email);
        user.setPassword(new RandomValueStringGenerator(300).generate());
        ResultActions result = createUserAndReturnResult(user, scimReadWriteToken, null, null);
        result.andExpect(status().isBadRequest()).andExpect(jsonPath("$.error").value("invalid_password"))
                .andExpect(jsonPath("$.message").value("Password must be no more than 255 characters in length."))
                .andExpect(jsonPath("$.error_description")
                        .value("Password must be no more than 255 characters in length."));
    }

    @Test
    public void test_Create_User_More_Than_One_Email() throws Exception {
        ScimUser scimUser = getScimUser();
        String secondEmail = "joe@" + generator.generate().toLowerCase() + ".com";
        scimUser.addEmail(secondEmail);
        createUserAndReturnResult(scimUser, scimReadWriteToken, null, null).andExpect(status().isBadRequest());
    }

    @Test
    public void testCreateUser() throws Exception {
        createUser(scimReadWriteToken);
    }

    @Test
    public void testCreateUserWithScimCreateToken() throws Exception {
        createUser(scimCreateToken);
    }

    @Test
    public void createUserWithUaaAdminToken() throws Exception {
        createUser(uaaAdminToken);
    }

    @Test
    public void createUserInOtherZoneWithUaaAdminToken() throws Exception {
        IdentityZone otherIdentityZone = getIdentityZone();

        createUser(getScimUser(), uaaAdminToken, IdentityZone.getUaa().getSubdomain(), otherIdentityZone.getId());
    }

    @Test
    public void default_password_policy_does_not_allow_empty_passwords() throws Exception {
        IdentityZone otherIdentityZone = getIdentityZone();
        ScimUser scimUser = getScimUser();
        scimUser.setPassword("");

        IdentityProvider<UaaIdentityProviderDefinition> uaa = getWebApplicationContext()
                .getBean(JdbcIdentityProviderProvisioning.class)
                .retrieveByOrigin(OriginKeys.UAA, otherIdentityZone.getId());

        ResultActions result = createUserAndReturnResult(scimUser, uaaAdminToken,
                IdentityZone.getUaa().getSubdomain(), otherIdentityZone.getId());
        result.andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.message").value("Password must be at least 1 characters in length."));
    }

    @Test
    public void createUserInOtherZoneWithUaaAdminTokenFromNonDefaultZone() throws Exception {
        IdentityZone identityZone = getIdentityZone();

        String authorities = "uaa.admin";
        clientDetails = utils().createClient(this.getMockMvc(), uaaAdminToken, "testClientId", "testClientSecret",
                null, null, Collections.singletonList("client_credentials"), authorities, null, identityZone);
        String uaaAdminTokenFromOtherZone = testClient.getClientCredentialsOAuthAccessToken("testClientId",
                "testClientSecret", "uaa.admin", identityZone.getSubdomain());

        byte[] requestBody = JsonUtils.writeValueAsBytes(getScimUser());
        MockHttpServletRequestBuilder post = post("/Users")
                .header("Authorization", "Bearer " + uaaAdminTokenFromOtherZone).contentType(APPLICATION_JSON)
                .content(requestBody);
        post.with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost"));
        post.header(IdentityZoneSwitchingFilter.HEADER, IdentityZone.getUaa().getId());

        getMockMvc().perform(post).andExpect(status().isForbidden());
    }

    @Test
    public void verification_link() throws Exception {
        ScimUser joel = setUpScimUser();

        MockHttpServletRequestBuilder get = setUpVerificationLinkRequest(joel, scimCreateToken);

        MvcResult result = getMockMvc().perform(get).andExpect(status().isOk()).andReturn();

        VerificationResponse verificationResponse = JsonUtils.readValue(result.getResponse().getContentAsString(),
                VerificationResponse.class);
        assertThat(verificationResponse.getVerifyLink().toString(), startsWith("http://localhost/verify_user"));

        String query = verificationResponse.getVerifyLink().getQuery();

        String code = getQueryStringParam(query, "code");
        assertThat(code, is(notNullValue()));

        ExpiringCode expiringCode = codeStore.retrieveCode(code);
        assertThat(expiringCode.getExpiresAt().getTime(), is(greaterThan(System.currentTimeMillis())));
        assertThat(expiringCode.getIntent(), is(REGISTRATION.name()));
        Map<String, String> data = JsonUtils.readValue(expiringCode.getData(),
                new TypeReference<Map<String, String>>() {
                });
        assertThat(data.get(InvitationConstants.USER_ID), is(notNullValue()));
        assertThat(data.get(CLIENT_ID), is(clientDetails.getClientId()));
        assertThat(data.get(REDIRECT_URI), is(HTTP_REDIRECT_EXAMPLE_COM));
    }

    @Test
    public void verification_link_in_non_default_zone() throws Exception {
        String subdomain = generator.generate().toLowerCase();
        MockMvcUtils.IdentityZoneCreationResult zoneResult = utils()
                .createOtherIdentityZoneAndReturnResult(subdomain, getMockMvc(), getWebApplicationContext(), null);
        String zonedClientId = "zonedClientId";
        String zonedClientSecret = "zonedClientSecret";
        BaseClientDetails zonedClientDetails = (BaseClientDetails) utils().createClient(this.getMockMvc(),
                zoneResult.getZoneAdminToken(), zonedClientId, zonedClientSecret, Collections.singleton("oauth"),
                null, Arrays.asList(new String[] { "client_credentials" }), "scim.create", null,
                zoneResult.getIdentityZone());
        zonedClientDetails.setClientSecret(zonedClientSecret);
        String zonedScimCreateToken = utils().getClientCredentialsOAuthAccessToken(getMockMvc(),
                zonedClientDetails.getClientId(), zonedClientDetails.getClientSecret(), "scim.create", subdomain);

        ScimUser joel = setUpScimUser(zoneResult.getIdentityZone());

        MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/Users/" + joel.getId() + "/verify-link")
                .header("Host", subdomain + ".localhost").header("Authorization", "Bearer " + zonedScimCreateToken)
                .param("redirect_uri", HTTP_REDIRECT_EXAMPLE_COM).accept(APPLICATION_JSON);

        MvcResult result = getMockMvc().perform(get).andExpect(status().isOk()).andReturn();
        VerificationResponse verificationResponse = JsonUtils.readValue(result.getResponse().getContentAsString(),
                VerificationResponse.class);
        assertThat(verificationResponse.getVerifyLink().toString(),
                startsWith("http://" + subdomain + ".localhost/verify_user"));

        String query = verificationResponse.getVerifyLink().getQuery();

        String code = getQueryStringParam(query, "code");
        assertThat(code, is(notNullValue()));

        ExpiringCode expiringCode = codeStore.retrieveCode(code);
        assertThat(expiringCode.getExpiresAt().getTime(), is(greaterThan(System.currentTimeMillis())));
        assertThat(expiringCode.getIntent(), is(REGISTRATION.name()));
        Map<String, String> data = JsonUtils.readValue(expiringCode.getData(),
                new TypeReference<Map<String, String>>() {
                });
        assertThat(data.get(InvitationConstants.USER_ID), is(notNullValue()));
        assertThat(data.get(CLIENT_ID), is(zonedClientDetails.getClientId()));
        assertThat(data.get(REDIRECT_URI), is(HTTP_REDIRECT_EXAMPLE_COM));
    }

    @Test
    public void verification_link_in_non_default_zone_using_switch() throws Exception {
        String subdomain = generator.generate().toLowerCase();
        MockMvcUtils.IdentityZoneCreationResult zoneResult = utils()
                .createOtherIdentityZoneAndReturnResult(subdomain, getMockMvc(), getWebApplicationContext(), null);
        String zonedClientId = "admin";
        String zonedClientSecret = "adminsecret";
        String zonedScimCreateToken = utils().getClientCredentialsOAuthAccessToken(getMockMvc(), zonedClientId,
                zonedClientSecret, "uaa.admin", null);

        ScimUser joel = setUpScimUser(zoneResult.getIdentityZone());

        MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/Users/" + joel.getId() + "/verify-link")
                .header("Host", "localhost").header("Authorization", "Bearer " + zonedScimCreateToken)
                .header(IdentityZoneSwitchingFilter.SUBDOMAIN_HEADER, subdomain)
                .param("redirect_uri", HTTP_REDIRECT_EXAMPLE_COM).accept(APPLICATION_JSON);

        MvcResult result = getMockMvc().perform(get).andExpect(status().isOk()).andReturn();
        VerificationResponse verificationResponse = JsonUtils.readValue(result.getResponse().getContentAsString(),
                VerificationResponse.class);
        assertThat(verificationResponse.getVerifyLink().toString(),
                startsWith("http://" + subdomain + ".localhost/verify_user"));

        String query = verificationResponse.getVerifyLink().getQuery();

        String code = getQueryStringParam(query, "code");
        assertThat(code, is(notNullValue()));

        ExpiringCode expiringCode = codeStore.retrieveCode(code);
        assertThat(expiringCode.getExpiresAt().getTime(), is(greaterThan(System.currentTimeMillis())));
        assertThat(expiringCode.getIntent(), is(REGISTRATION.name()));
        Map<String, String> data = JsonUtils.readValue(expiringCode.getData(),
                new TypeReference<Map<String, String>>() {
                });
        assertThat(data.get(InvitationConstants.USER_ID), is(notNullValue()));
        assertThat(data.get(CLIENT_ID), is("admin"));
        assertThat(data.get(REDIRECT_URI), is(HTTP_REDIRECT_EXAMPLE_COM));
    }

    @Test
    public void create_user_without_username() throws Exception {
        ScimUser user = new ScimUser(null, null, "Joel", "D'sa");
        user.setPassword("password");
        user.setPrimaryEmail("test@test.org");

        getMockMvc()
                .perform(post("/Users").header("Authorization", "Bearer " + scimReadWriteToken)
                        .contentType(APPLICATION_JSON).content(JsonUtils.writeValueAsString(user)))
                .andExpect(status().isBadRequest())
                .andExpect(content().string(JsonObjectMatcherUtils.matchesJsonObject(new JSONObject()
                        .put("error_description", "A username must be provided.")
                        .put("message", "A username must be provided.").put("error", "invalid_scim_resource"))));
    }

    @Test
    public void create_user_without_email() throws Exception {
        ScimUser user = new ScimUser(null, "a_user", "Joel", "D'sa");
        user.setPassword("password");

        getMockMvc()
                .perform(post("/Users").header("Authorization", "Bearer " + scimReadWriteToken)
                        .contentType(APPLICATION_JSON).content(JsonUtils.writeValueAsString(user)))
                .andExpect(status().isBadRequest())
                .andExpect(content().string(JsonObjectMatcherUtils.matchesJsonObject(
                        new JSONObject().put("error_description", "Exactly one email must be provided.")
                                .put("message", "Exactly one email must be provided.")
                                .put("error", "invalid_scim_resource"))));
    }

    @Test
    public void create_user_then_update_without_email() throws Exception {
        ScimUser user = setUpScimUser();
        user.setEmails(null);

        getMockMvc()
                .perform(put("/Users/" + user.getId()).header("Authorization", "Bearer " + scimReadWriteToken)
                        .header("If-Match", "\"" + user.getVersion() + "\"").contentType(APPLICATION_JSON)
                        .content(JsonUtils.writeValueAsString(user)))
                .andExpect(status().isBadRequest())
                .andExpect(content().string(JsonObjectMatcherUtils.matchesJsonObject(
                        new JSONObject().put("error_description", "Exactly one email must be provided.")
                                .put("message", "Exactly one email must be provided.")
                                .put("error", "invalid_scim_resource"))));
    }

    @Test
    public void patch_user_to_inactive_then_login() throws Exception {
        ScimUser user = setUpScimUser();
        user.setVerified(true);
        boolean active = true;
        user.setActive(active);
        getMockMvc()
                .perform(patch("/Users/" + user.getId()).header("Authorization", "Bearer " + scimReadWriteToken)
                        .header("If-Match", "\"" + user.getVersion() + "\"").contentType(APPLICATION_JSON)
                        .content(JsonUtils.writeValueAsString(user)))
                .andExpect(status().isOk()).andExpect(jsonPath("$.active", equalTo(active)));

        performAuthentication(user, true);

        active = false;
        user.setActive(active);
        getMockMvc()
                .perform(patch("/Users/" + user.getId()).header("Authorization", "Bearer " + scimReadWriteToken)
                        .header("If-Match", "\"" + (user.getVersion() + 1) + "\"").contentType(APPLICATION_JSON)
                        .content(JsonUtils.writeValueAsString(user)))
                .andExpect(status().isOk()).andExpect(jsonPath("$.active", equalTo(active)));

        performAuthentication(user, false);

    }

    public void performAuthentication(ScimUser user, boolean success) throws Exception {
        getMockMvc()
                .perform(post("/login.do").accept("text/html").with(cookieCsrf())
                        .param("username", user.getUserName()).param("password", USER_PASSWORD))
                .andDo(print()).andExpect(success ? authenticated() : unauthenticated());
    }

    @Test
    public void verification_link_unverified_error() throws Exception {
        ScimUser user = setUpScimUser();
        user.setVerified(true);
        usersRepository.update(user.getId(), user);

        MockHttpServletRequestBuilder get = setUpVerificationLinkRequest(user, scimCreateToken);

        getMockMvc().perform(get).andExpect(status().isMethodNotAllowed())
                .andExpect(content().string(JsonObjectMatcherUtils.matchesJsonObject(new JSONObject()
                        .put("error_description", UserAlreadyVerifiedException.DESC)
                        .put("message", UserAlreadyVerifiedException.DESC).put("error", "user_already_verified"))));
    }

    @Test
    public void verification_link_is_authorized_endpoint() throws Exception {
        ScimUser joel = setUpScimUser();

        MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/Users/" + joel.getId() + "/verify-link")
                .param("redirect_uri", HTTP_REDIRECT_EXAMPLE_COM).accept(APPLICATION_JSON);

        getMockMvc().perform(get).andExpect(status().isUnauthorized());
    }

    @Test
    public void verification_link_secured_with_scimcreate() throws Exception {
        ScimUser joel = setUpScimUser();

        MockHttpServletRequestBuilder get = setUpVerificationLinkRequest(joel, scimReadWriteToken);

        getMockMvc().perform(get).andExpect(status().isForbidden());
    }

    @Test
    public void verification_link_user_not_found() throws Exception {
        MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/Users/12345/verify-link")
                .header("Authorization", "Bearer " + scimCreateToken)
                .param("redirect_uri", HTTP_REDIRECT_EXAMPLE_COM).accept(APPLICATION_JSON);

        getMockMvc().perform(get).andExpect(status().isNotFound())
                .andExpect(content().string(JsonObjectMatcherUtils.matchesJsonObject(new JSONObject()
                        .put("error_description", "User 12345 does not exist")
                        .put("message", "User 12345 does not exist").put("error", "scim_resource_not_found"))));
    }

    @Test
    public void listUsers_in_anotherZone() throws Exception {
        String subdomain = generator.generate();
        MockMvcUtils.IdentityZoneCreationResult result = utils().createOtherIdentityZoneAndReturnResult(subdomain,
                getMockMvc(), getWebApplicationContext(), null);
        String zoneAdminToken = result.getZoneAdminToken();
        createUser(getScimUser(), zoneAdminToken, IdentityZone.getUaa().getSubdomain(),
                result.getIdentityZone().getId());

        MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/Users")
                .header("X-Identity-Zone-Subdomain", subdomain).header("Authorization", "Bearer " + zoneAdminToken)
                .accept(APPLICATION_JSON);

        MvcResult mvcResult = getMockMvc().perform(get).andExpect(status().isOk()).andReturn();
        SearchResults searchResults = JsonUtils.readValue(mvcResult.getResponse().getContentAsString(),
                SearchResults.class);
        MatcherAssert.assertThat(searchResults.getResources().size(), is(1));

    }

    @Test
    public void testVerifyUser() throws Exception {
        verifyUser(scimReadWriteToken);
    }

    @Test
    public void testVerifyUserWithScimCreateToken() throws Exception {
        verifyUser(scimCreateToken);
    }

    @Test
    public void testCreateUserInZoneUsingAdminClient() throws Exception {
        String subdomain = generator.generate();
        mockMvcUtils.createOtherIdentityZone(subdomain, getMockMvc(), getWebApplicationContext());

        String zoneAdminToken = testClient.getClientCredentialsOAuthAccessToken("admin", "admin-secret",
                "scim.write", subdomain);

        createUser(zoneAdminToken, subdomain);
    }

    @Test
    public void testCreateUserInZoneUsingZoneAdminUser() throws Exception {
        String subdomain = generator.generate();
        MockMvcUtils.IdentityZoneCreationResult result = utils().createOtherIdentityZoneAndReturnResult(subdomain,
                getMockMvc(), getWebApplicationContext(), null);
        String zoneAdminToken = result.getZoneAdminToken();
        createUser(getScimUser(), zoneAdminToken, IdentityZone.getUaa().getSubdomain(),
                result.getIdentityZone().getId());
    }

    @Test
    public void testUserSelfAccess_Get_and_Post() throws Exception {
        ScimUser user = getScimUser();
        user.setPassword("secret");
        user = createUser(user, scimReadWriteToken, IdentityZone.getUaa().getSubdomain());

        String selfToken = testClient.getUserOAuthAccessToken("cf", "", user.getUserName(), "secret", "");

        user.setName(new ScimUser.Name("Given1", "Family1"));
        user = updateUser(selfToken, HttpStatus.OK.value(), user);

        user = getAndReturnUser(HttpStatus.OK.value(), user, selfToken);
    }

    @Test
    public void testCreateUserInOtherZoneIsUnauthorized() throws Exception {
        String subdomain = generator.generate();
        mockMvcUtils.createOtherIdentityZone(subdomain, getMockMvc(), getWebApplicationContext());

        String otherSubdomain = generator.generate();
        mockMvcUtils.createOtherIdentityZone(otherSubdomain, getMockMvc(), getWebApplicationContext());

        String zoneAdminToken = testClient.getClientCredentialsOAuthAccessToken("admin", "admin-secret",
                "scim.write", subdomain);

        ScimUser user = getScimUser();

        byte[] requestBody = JsonUtils.writeValueAsBytes(user);
        MockHttpServletRequestBuilder post = post("/Users")
                .with(new SetServerNameRequestPostProcessor(otherSubdomain + ".localhost"))
                .header("Authorization", "Bearer " + zoneAdminToken).contentType(APPLICATION_JSON)
                .content(requestBody);

        getMockMvc().perform(post).andExpect(status().isUnauthorized());
    }

    @Test
    public void testUnlockAccount() throws Exception {
        ScimUser userToLockout = createUser(uaaAdminToken);
        attemptFailedLogin(5, userToLockout.getUserName(), "");

        UserAccountStatus alteredAccountStatus = new UserAccountStatus();
        alteredAccountStatus.setLocked(false);
        updateAccountStatus(userToLockout, alteredAccountStatus).andExpect(status().isOk())
                .andExpect(content().json(JsonUtils.writeValueAsString(alteredAccountStatus)));

        attemptLogin(userToLockout).andExpect(redirectedUrl("/"));
    }

    @Test
    public void testAccountStatusEmptyPatchDoesNotUnlock() throws Exception {
        ScimUser userToLockout = createUser(uaaAdminToken);
        attemptFailedLogin(5, userToLockout.getUserName(), "");

        updateAccountStatus(userToLockout, new UserAccountStatus()).andExpect(status().isOk())
                .andExpect(content().json("{}"));

        attemptLogin(userToLockout).andExpect(redirectedUrl("/login?error=account_locked"));
    }

    @Test
    public void testUpdateStatusCannotLock() throws Exception {
        ScimUser user = createUser(uaaAdminToken);

        UserAccountStatus alteredAccountStatus = new UserAccountStatus();
        alteredAccountStatus.setLocked(true);
        updateAccountStatus(user, alteredAccountStatus).andExpect(status().isBadRequest());

        attemptLogin(user).andExpect(redirectedUrl("/"));
    }

    @Test
    public void testUnlockAccountWhenNotLocked() throws Exception {
        ScimUser userToLockout = createUser(uaaAdminToken);

        UserAccountStatus alteredAccountStatus = new UserAccountStatus();
        alteredAccountStatus.setLocked(false);
        updateAccountStatus(userToLockout, alteredAccountStatus).andExpect(status().isOk())
                .andExpect(content().json(JsonUtils.writeValueAsString(alteredAccountStatus)));

        attemptLogin(userToLockout).andExpect(redirectedUrl("/"));
    }

    @Test
    public void testForcePasswordExpireAccountInvalid() throws Exception {
        ScimUser user = createUser(uaaAdminToken);
        UserAccountStatus alteredAccountStatus = new UserAccountStatus();
        alteredAccountStatus.setPasswordChangeRequired(false);

        updateAccountStatus(user, alteredAccountStatus).andExpect(status().isBadRequest());

        assertFalse(usersRepository.checkPasswordChangeIndividuallyRequired(user.getId()));
    }

    @Test
    public void testForcePasswordExpireAccountExternalUser() throws Exception {
        ScimUser user = createUser(uaaAdminToken);
        user.setOrigin("NOT_UAA");
        updateUser(uaaAdminToken, HttpStatus.OK.value(), user);
        UserAccountStatus alteredAccountStatus = new UserAccountStatus();
        alteredAccountStatus.setPasswordChangeRequired(true);

        updateAccountStatus(user, alteredAccountStatus).andExpect(status().isBadRequest());

        assertFalse(usersRepository.checkPasswordChangeIndividuallyRequired(user.getId()));
    }

    @Test
    public void testForcePasswordChange() throws Exception {
        ScimUser user = createUser(uaaAdminToken);

        assertFalse(usersRepository.checkPasswordChangeIndividuallyRequired(user.getId()));

        UserAccountStatus alteredAccountStatus = new UserAccountStatus();
        alteredAccountStatus.setPasswordChangeRequired(true);

        updateAccountStatus(user, alteredAccountStatus).andExpect(status().isOk())
                .andExpect(content().json(JsonUtils.writeValueAsString(alteredAccountStatus)));

        assertTrue(usersRepository.checkPasswordChangeIndividuallyRequired(user.getId()));
    }

    @Test
    public void testTryMultipleStatusUpdatesWithInvalidLock() throws Exception {
        ScimUser user = createUser(uaaAdminToken);

        UserAccountStatus alteredAccountStatus = new UserAccountStatus();
        alteredAccountStatus.setPasswordChangeRequired(true);
        alteredAccountStatus.setLocked(true);

        updateAccountStatus(user, alteredAccountStatus).andExpect(status().isBadRequest());

        assertFalse(usersRepository.checkPasswordChangeIndividuallyRequired(user.getId()));

        attemptLogin(user).andExpect(redirectedUrl("/"));
    }

    @Test
    public void testTryMultipleStatusUpdatesWithInvalidRemovalOfPasswordChange() throws Exception {
        ScimUser user = createUser(uaaAdminToken);
        attemptFailedLogin(5, user.getUserName(), "");

        UserAccountStatus alteredAccountStatus = new UserAccountStatus();
        alteredAccountStatus.setPasswordChangeRequired(false);
        alteredAccountStatus.setLocked(false);

        updateAccountStatus(user, alteredAccountStatus).andExpect(status().isBadRequest());

        assertFalse(usersRepository.checkPasswordChangeIndividuallyRequired(user.getId()));

        attemptLogin(user).andExpect(redirectedUrl("/login?error=account_locked"));
    }

    private ResultActions updateAccountStatus(ScimUser user, UserAccountStatus alteredAccountStatus)
            throws Exception {
        String jsonStatus = JsonUtils.writeValueAsString(alteredAccountStatus);
        return getMockMvc().perform(
                patch("/Users/" + user.getId() + "/status").header("Authorization", "Bearer " + uaaAdminToken)
                        .accept(APPLICATION_JSON).contentType(APPLICATION_JSON).content(jsonStatus));
    }

    private ResultActions attemptLogin(ScimUser user) throws Exception {
        return getMockMvc().perform(post("/login.do").with(cookieCsrf()).param("username", user.getUserName())
                .param("password", user.getPassword()));
    }

    private void attemptFailedLogin(int numberOfAttempts, String username, String subdomain) throws Exception {
        String requestDomain = subdomain.equals("") ? "localhost" : subdomain + ".localhost";
        MockHttpServletRequestBuilder post = post("/login.do")
                .with(new SetServerNameRequestPostProcessor(requestDomain)).with(cookieCsrf())
                .param("username", username).param("password", "wrong_password");
        for (int i = 0; i < numberOfAttempts; i++) {
            getMockMvc().perform(post).andExpect(redirectedUrl("/login?error=login_failure"));
        }
    }

    private void verifyUser(String token) throws Exception {
        ScimUserProvisioning usersRepository = getWebApplicationContext().getBean(ScimUserProvisioning.class);
        String email = "joe@" + generator.generate().toLowerCase() + ".com";
        ScimUser joel = new ScimUser(null, email, "Joel", "D'sa");
        joel.addEmail(email);
        joel = usersRepository.createUser(joel, "pas5Word");

        MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/Users/" + joel.getId() + "/verify")
                .header("Authorization", "Bearer " + token).accept(APPLICATION_JSON);

        getMockMvc().perform(get).andExpect(status().isOk()).andExpect(header().string("ETag", "\"0\""))
                .andExpect(jsonPath("$.userName").value(email))
                .andExpect(jsonPath("$.emails[0].value").value(email))
                .andExpect(jsonPath("$.name.familyName").value("D'sa"))
                .andExpect(jsonPath("$.name.givenName").value("Joel"))
                .andExpect(jsonPath("$.verified").value(true));
    }

    private void getUser(String token, int status) throws Exception {
        ScimUser joel = setUpScimUser();

        getAndReturnUser(status, joel, token);
    }

    protected ScimUser getAndReturnUser(int status, ScimUser user, String token) throws Exception {
        MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/Users/" + user.getId())
                .header("Authorization", "Bearer " + token).accept(APPLICATION_JSON);

        if (status == HttpStatus.OK.value()) {
            String json = getMockMvc().perform(get).andExpect(status().is(status))
                    .andExpect(header().string("ETag", "\"" + user.getVersion() + "\""))
                    .andExpect(jsonPath("$.userName").value(user.getPrimaryEmail()))
                    .andExpect(jsonPath("$.emails[0].value").value(user.getPrimaryEmail()))
                    .andExpect(jsonPath("$.name.familyName").value(user.getFamilyName()))
                    .andExpect(jsonPath("$.name.givenName").value(user.getGivenName())).andReturn().getResponse()
                    .getContentAsString();
            return JsonUtils.readValue(json, ScimUser.class);
        } else {
            getMockMvc().perform(get).andExpect(status().is(status));
            return null;
        }
    }

    @Test
    public void testGetUser() throws Exception {
        getUser(scimReadWriteToken, HttpStatus.OK.value());
    }

    @Test
    public void testGetUserWithInvalidAttributes() throws Exception {

        String nonexistentAttribute = "displayBlaBla";

        MockHttpServletRequestBuilder get = get("/Users").header("Authorization", "Bearer " + scimReadWriteToken)
                .contentType(MediaType.APPLICATION_JSON).param("attributes", nonexistentAttribute)
                .accept(APPLICATION_JSON);

        MvcResult mvcResult = getMockMvc().perform(get).andExpect(status().isOk()).andReturn();

        String body = mvcResult.getResponse().getContentAsString();

        List<Map> attList = (List) JsonUtils.readValue(body, Map.class).get("resources");
        for (Map<String, Object> attMap : attList) {
            assertNull(attMap.get(nonexistentAttribute));
        }
    }

    @Test
    public void testGetUserWithScimCreateToken() throws Exception {
        getUser(scimCreateToken, HttpStatus.FORBIDDEN.value());
    }

    @Test
    public void getUsersWithUaaAdminToken() throws Exception {
        setUpScimUser();

        MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/Users")
                .header("Authorization", "Bearer " + uaaAdminToken).accept(APPLICATION_JSON);

        getMockMvc().perform(get).andExpect(status().isOk());

    }

    @Test
    public void getUserFromOtherZoneWithUaaAdminToken() throws Exception {
        IdentityZone otherIdentityZone = getIdentityZone();

        ScimUser user = setUpScimUser(otherIdentityZone);

        MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/Users/", user.getId())
                .header("Authorization", "Bearer " + uaaAdminToken).accept(APPLICATION_JSON);

        getMockMvc().perform(get).andExpect(status().isOk());

    }

    protected ScimUser updateUser(String token, int status) throws Exception {
        ScimUserProvisioning usersRepository = getWebApplicationContext().getBean(ScimUserProvisioning.class);
        String email = "otheruser@" + generator.generate().toLowerCase() + ".com";
        ScimUser user = new ScimUser(null, email, "Other", "User");
        user.addEmail(email);
        user = usersRepository.createUser(user, "pas5Word");
        if (status == HttpStatus.BAD_REQUEST.value()) {
            user.setUserName(null);
        } else {
            String username2 = "ou" + generator.generate().toLowerCase();
            user.setUserName(username2);
        }

        user.setName(new ScimUser.Name("Joe", "Smith"));

        return updateUser(token, status, user);
    }

    protected ScimUser updateUser(String token, int status, ScimUser user) throws Exception {
        MockHttpServletRequestBuilder put = put("/Users/" + user.getId()).header("Authorization", "Bearer " + token)
                .header("If-Match", "\"" + user.getVersion() + "\"").accept(APPLICATION_JSON)
                .contentType(APPLICATION_JSON).content(JsonUtils.writeValueAsBytes(user));
        if (status == HttpStatus.OK.value()) {
            String json = getMockMvc().perform(put).andExpect(status().isOk())
                    .andExpect(header().string("ETag", "\"1\""))
                    .andExpect(jsonPath("$.userName").value(user.getUserName()))
                    .andExpect(jsonPath("$.emails[0].value").value(user.getPrimaryEmail()))
                    .andExpect(jsonPath("$.name.givenName").value(user.getGivenName()))
                    .andExpect(jsonPath("$.name.familyName").value(user.getFamilyName())).andReturn().getResponse()
                    .getContentAsString();

            return JsonUtils.readValue(json, ScimUser.class);
        } else {
            getMockMvc().perform(put).andExpect(status().is(status));
            return null;
        }
    }

    @Test
    public void testUpdateUser() throws Exception {
        updateUser(scimReadWriteToken, HttpStatus.OK.value());
    }

    @Test
    public void testUpdateUser_No_Username_Returns_400() throws Exception {
        updateUser(scimReadWriteToken, HttpStatus.BAD_REQUEST.value());
    }

    @Test
    public void testUpdateUserWithScimCreateToken() throws Exception {
        updateUser(scimCreateToken, HttpStatus.FORBIDDEN.value());
    }

    @Test
    public void testUpdateUserWithUaaAdminToken() throws Exception {
        updateUser(uaaAdminToken, HttpStatus.OK.value());
    }

    @Test
    public void testUpdateUserInOtherZoneWithUaaAdminToken() throws Exception {
        IdentityZone identityZone = getIdentityZone();
        ScimUser user = setUpScimUser(identityZone);
        user.setName(new ScimUser.Name("changed", "name"));

        getMockMvc()
                .perform(put("/Users/" + user.getId()).header("Authorization", "Bearer " + uaaAdminToken)
                        .header(IdentityZoneSwitchingFilter.HEADER, identityZone.getId())
                        .header("If-Match", "\"" + user.getVersion() + "\"").contentType(APPLICATION_JSON)
                        .content(JsonUtils.writeValueAsBytes(user)))
                .andExpect(status().isOk()).andExpect(header().string("ETag", "\"1\""))
                .andExpect(jsonPath("$.userName").value(user.getUserName()))
                .andExpect(jsonPath("$.emails[0].value").value(user.getPrimaryEmail()))
                .andExpect(jsonPath("$.name.givenName").value(user.getGivenName()))
                .andExpect(jsonPath("$.name.familyName").value(user.getFamilyName()));
    }

    @Test
    public void testDeleteUserWithUaaAdminToken() throws Exception {
        ScimUser user = setUpScimUser();

        getMockMvc()
                .perform((delete("/Users/" + user.getId())).header("Authorization", "Bearer " + uaaAdminToken)
                        .contentType(APPLICATION_JSON).content(JsonUtils.writeValueAsBytes(user)))
                .andExpect(status().isOk()).andExpect(jsonPath("$.userName").value(user.getUserName()))
                .andExpect(jsonPath("$.emails[0].value").value(user.getPrimaryEmail()))
                .andExpect(jsonPath("$.name.givenName").value(user.getGivenName()))
                .andExpect(jsonPath("$.name.familyName").value(user.getFamilyName()));
    }

    @Test
    public void testDeleteUserInOtherZoneWithUaaAdminToken() throws Exception {
        IdentityZone identityZone = getIdentityZone();
        ScimUser user = setUpScimUser(identityZone);

        getMockMvc()
                .perform((delete("/Users/" + user.getId())).header("Authorization", "Bearer " + uaaAdminToken)
                        .header(IdentityZoneSwitchingFilter.HEADER, identityZone.getId())
                        .contentType(APPLICATION_JSON).content(JsonUtils.writeValueAsBytes(user)))
                .andExpect(status().isOk()).andExpect(jsonPath("$.userName").value(user.getUserName()))
                .andExpect(jsonPath("$.emails[0].value").value(user.getPrimaryEmail()))
                .andExpect(jsonPath("$.name.givenName").value(user.getGivenName()))
                .andExpect(jsonPath("$.name.familyName").value(user.getFamilyName()));
    }

    @Test
    public void cannotCreateUserWithInvalidPasswordInDefaultZone() throws Exception {
        ScimUser user = getScimUser();
        user.setPassword(new RandomValueStringGenerator(260).generate());
        byte[] requestBody = JsonUtils.writeValueAsBytes(user);
        MockHttpServletRequestBuilder post = post("/Users").header("Authorization", "Bearer " + scimCreateToken)
                .contentType(APPLICATION_JSON).content(requestBody);

        getMockMvc().perform(post).andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.error").value("invalid_password"))
                .andExpect(jsonPath("$.message").value("Password must be no more than 255 characters in length."));
    }

    private MockHttpServletRequestBuilder setUpVerificationLinkRequest(ScimUser user, String token) {
        return MockMvcRequestBuilders.get("/Users/" + user.getId() + "/verify-link")
                .header("Authorization", "Bearer " + token).param("redirect_uri", HTTP_REDIRECT_EXAMPLE_COM)
                .accept(APPLICATION_JSON);
    }

    private ScimUser setUpScimUser() {
        return setUpScimUser(IdentityZoneHolder.get());
    }

    private ScimUser setUpScimUser(IdentityZone zone) {
        IdentityZone original = IdentityZoneHolder.get();
        try {
            IdentityZoneHolder.set(zone);
            String email = "joe@" + generator.generate().toLowerCase() + ".com";
            ScimUser joel = new ScimUser(null, email, "Joel", "D'sa");
            joel.setVerified(false);
            joel.addEmail(email);
            joel = usersRepository.createUser(joel, USER_PASSWORD);
            return joel;
        } finally {
            IdentityZoneHolder.set(original);
        }
    }

    private String getQueryStringParam(String query, String key) {
        List<NameValuePair> params = URLEncodedUtils.parse(query, Charset.defaultCharset());
        for (NameValuePair pair : params) {
            if (key.equals(pair.getName())) {
                return pair.getValue();
            }
        }
        return null;
    }

    private IdentityZone getIdentityZone() throws Exception {
        String subdomain = generator.generate();
        return mockMvcUtils.createOtherIdentityZone(subdomain, getMockMvc(), getWebApplicationContext());
    }
}