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

Java tutorial

Introduction

Here is the source code for org.cloudfoundry.identity.uaa.scim.endpoints.ScimGroupEndpointsMockMvcTests.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.commons.codec.binary.Base64;
import org.cloudfoundry.identity.uaa.constants.OriginKeys;
import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest;
import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils;
import org.cloudfoundry.identity.uaa.resources.SearchResults;
import org.cloudfoundry.identity.uaa.scim.ScimGroup;
import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMember;
import org.cloudfoundry.identity.uaa.scim.ScimGroupMember;
import org.cloudfoundry.identity.uaa.scim.ScimGroupMembershipManager;
import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning;
import org.cloudfoundry.identity.uaa.scim.ScimUser;
import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning;
import org.cloudfoundry.identity.uaa.scim.bootstrap.ScimExternalGroupBootstrap;
import org.cloudfoundry.identity.uaa.scim.exception.MemberAlreadyExistsException;
import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupExternalMembershipManager;
import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupProvisioning;
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.cloudfoundry.identity.uaa.zone.ZoneManagementScopes;
import org.hamcrest.Matcher;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
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.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static org.cloudfoundry.identity.uaa.constants.OriginKeys.LDAP;
import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.utils;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
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.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.util.StringUtils.hasText;

public class ScimGroupEndpointsMockMvcTests extends InjectedMockContextTest {

    private static List<String> originalDefaultExternalMembers;
    private static List<ScimGroupExternalMember> originalDatabaseExternalMembers;

    private String scimReadToken;
    private String scimWriteToken;
    private String scimReadUserToken;
    private String identityClientToken;
    private ScimUser scimUser;
    private RandomValueStringGenerator generator = new RandomValueStringGenerator();
    private List<String> defaultExternalMembers;
    private List<ScimGroupExternalMember> databaseExternalMembers;
    private String clientId;
    private String clientSecret;
    private JdbcTemplate template;
    private ScimExternalGroupBootstrap bootstrap;

    private ArrayList<String[]> ephemeralResources = new ArrayList<>();

    @Before
    public void setUp() throws Exception {
        if (originalDatabaseExternalMembers == null) {
            originalDefaultExternalMembers = (List<String>) getWebApplicationContext()
                    .getBean("defaultExternalMembers");
            originalDatabaseExternalMembers = getWebApplicationContext()
                    .getBean(JdbcScimGroupExternalMembershipManager.class).query("");
        }

        if (bootstrap == null) {
            bootstrap = getWebApplicationContext().getBean(ScimExternalGroupBootstrap.class);
        }

        if (template == null) {
            template = getWebApplicationContext().getBean(JdbcTemplate.class);
        }

        template.update("delete from external_group_mapping");
        bootstrap.afterPropertiesSet();

        String adminToken = testClient.getClientCredentialsOAuthAccessToken("admin", "adminsecret",
                "clients.read clients.write clients.secret clients.admin");
        clientId = generator.generate().toLowerCase();
        clientSecret = generator.generate().toLowerCase();
        String authorities = "scim.read,scim.write,password.write,oauth.approvals,scim.create";
        utils().createClient(this.getMockMvc(), adminToken, clientId, clientSecret, Collections.singleton("oauth"),
                Arrays.asList("foo", "bar", "scim.read"), Arrays.asList("client_credentials", "password"),
                authorities);
        scimReadToken = testClient.getClientCredentialsOAuthAccessToken(clientId, clientSecret,
                "scim.read password.write");
        scimWriteToken = testClient.getClientCredentialsOAuthAccessToken(clientId, clientSecret,
                "scim.write password.write");

        defaultExternalMembers = new LinkedList<>(originalDefaultExternalMembers);
        databaseExternalMembers = new LinkedList<>(originalDatabaseExternalMembers);

        scimUser = createUserAndAddToGroups(IdentityZone.getUaa(),
                new HashSet(Arrays.asList("scim.read", "scim.write", "scim.me")));
        scimReadUserToken = testClient.getUserOAuthAccessToken("cf", "", scimUser.getUserName(), "password",
                "scim.read");
        identityClientToken = testClient.getClientCredentialsOAuthAccessToken("identity", "identitysecret", "");
    }

    @After
    public void cleanUp() {
        for (Object[] resource : ephemeralResources) {
            template.update("delete from group_membership where member_id = ? and member_type = ?", resource);
        }
        ephemeralResources.clear();
    }

    @Test
    public void testIdentityClientManagesZoneAdmins() throws Exception {
        IdentityZone zone = utils().createZoneUsingWebRequest(getMockMvc(), identityClientToken);
        ScimGroupMember member = new ScimGroupMember(scimUser.getId());
        ScimGroup group = new ScimGroup(null, "zones." + zone.getId() + ".admin", zone.getId());
        group.setMembers(Arrays.asList(member));
        MockHttpServletRequestBuilder post = post("/Groups/zones").accept(APPLICATION_JSON)
                .contentType(APPLICATION_JSON).header("Authorization", "Bearer " + identityClientToken)
                .content(JsonUtils.writeValueAsBytes(group));
        //create the zones.{id}.admin
        getMockMvc().perform(post).andExpect(status().isCreated());

        MockHttpServletRequestBuilder delete = delete("/Groups/zones/{userId}/{zoneId}", scimUser.getId(),
                zone.getId()).header("Authorization", "Bearer " + identityClientToken);
        //delete the zones.{id}.admin
        getMockMvc().perform(delete).andExpect(status().isOk());
        //the relationship is not found
        getMockMvc().perform(delete).andExpect(status().isNotFound());

        //try a regular scim token
        getMockMvc().perform(post("/Groups/zones").accept(APPLICATION_JSON).contentType(APPLICATION_JSON)
                .header("Authorization", "Bearer " + scimWriteToken).content(JsonUtils.writeValueAsBytes(group)))
                .andExpect(status().isForbidden());

        getMockMvc().perform(delete("/Groups/zones/{userId}/{zoneId}", scimUser.getId(), zone.getId())
                .header("Authorization", "Bearer " + scimWriteToken)).andExpect(status().isForbidden());

        getMockMvc().perform(delete("/Groups/zones/{userId}/{zoneId}", "nonexistent", zone.getId())
                .header("Authorization", "Bearer " + identityClientToken)).andExpect(status().isNotFound());

        getMockMvc()
                .perform(post("/Groups/zones").accept(APPLICATION_JSON).contentType(APPLICATION_JSON)
                        .header("Authorization", "Bearer " + identityClientToken).content(""))
                .andExpect(status().isBadRequest());

        //add two users to the same zone
        for (int i = 0; i < 2; i++) {
            ScimUser user = createUserAndAddToGroups(IdentityZone.getUaa(),
                    new HashSet(Arrays.asList("scim.read", "scim.write", "scim.me")));
            member = new ScimGroupMember(user.getId());
            group = new ScimGroup(null, "zones." + zone.getId() + ".admin", zone.getId());
            group.setMembers(Arrays.asList(member));

            post = post("/Groups/zones").accept(APPLICATION_JSON).contentType(APPLICATION_JSON)
                    .header("Authorization", "Bearer " + identityClientToken)
                    .content(JsonUtils.writeValueAsBytes(group));
            //create the zones.{id}.admin
            getMockMvc().perform(post).andExpect(status().isCreated());
        }
    }

    @Test
    public void testLimitedScopesWithoutMember() throws Exception {
        IdentityZone zone = utils().createZoneUsingWebRequest(getMockMvc(), identityClientToken);
        ScimGroup group = new ScimGroup("zones." + zone.getId() + ".admin");

        MockHttpServletRequestBuilder post = post("/Groups/zones").accept(APPLICATION_JSON)
                .contentType(APPLICATION_JSON).header("Authorization", "Bearer " + identityClientToken)
                .content(JsonUtils.writeValueAsBytes(group));

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

    @Test
    public void add_and_Delete_Members_toZoneManagementGroups_withVariousGroupNames() throws Exception {
        addAndDeleteMemberstoZoneManagementGroups("zones.%s.admin", HttpStatus.CREATED, HttpStatus.OK);
        addAndDeleteMemberstoZoneManagementGroups("zones.%s.read", HttpStatus.CREATED, HttpStatus.OK);
        addAndDeleteMemberstoZoneManagementGroups("zones.%s.clients.read", HttpStatus.CREATED, HttpStatus.OK);
        addAndDeleteMemberstoZoneManagementGroups("zones.%s.clients.write", HttpStatus.CREATED, HttpStatus.OK);
        addAndDeleteMemberstoZoneManagementGroups("zones.%s.clients.admin", HttpStatus.CREATED, HttpStatus.OK);
        addAndDeleteMemberstoZoneManagementGroups("zones.%s.idps.read", HttpStatus.CREATED, HttpStatus.OK);

        addAndDeleteMemberstoZoneManagementGroups("zones.%s.blah.clients.read", HttpStatus.BAD_REQUEST, null);
        addAndDeleteMemberstoZoneManagementGroups("zones.%s.invalid", HttpStatus.BAD_REQUEST, null);

        addAndDeleteMemberstoZoneManagementGroups("zones..admin", HttpStatus.BAD_REQUEST, null);
    }

    private ResultActions[] addAndDeleteMemberstoZoneManagementGroups(String displayName, HttpStatus create,
            HttpStatus delete) throws Exception {
        ResultActions[] result = new ResultActions[2];
        IdentityZone zone = utils().createZoneUsingWebRequest(getMockMvc(), identityClientToken);
        ScimGroupMember member = new ScimGroupMember(scimUser.getId());
        ScimGroup group = new ScimGroup(String.format(displayName, zone.getId()));
        group.setMembers(Arrays.asList(member));

        result[0] = createZoneScope(group);
        result[0].andExpect(status().is(create.value()));

        if (delete != null) {
            result[1] = deleteZoneScope(zone, group);
            result[1].andExpect(status().is(delete.value()));
        }
        return result;
    }

    private ResultActions deleteZoneScope(IdentityZone zone, ScimGroup group) throws Exception {
        String removeS = String.format("zones.%s.", zone.getId());
        String scope = group.getDisplayName().substring(removeS.length());
        MockHttpServletRequestBuilder delete = delete("/Groups/zones/{userId}/{zoneId}/{scope}", scimUser.getId(),
                zone.getId(), scope).accept(APPLICATION_JSON).header("Authorization",
                        "Bearer " + identityClientToken);
        return getMockMvc().perform(delete);
    }

    private ResultActions createZoneScope(ScimGroup group) throws Exception {
        MockHttpServletRequestBuilder post = post("/Groups/zones").accept(APPLICATION_JSON)
                .contentType(APPLICATION_JSON).header("Authorization", "Bearer " + identityClientToken)
                .content(JsonUtils.writeValueAsBytes(group));
        return getMockMvc().perform(post);
    }

    @Test
    public void testGroupOperations_as_Zone_Admin() throws Exception {
        String subdomain = generator.generate();
        MockMvcUtils.IdentityZoneCreationResult result = utils().createOtherIdentityZoneAndReturnResult(subdomain,
                getMockMvc(), getWebApplicationContext(), null);
        String zoneAdminToken = result.getZoneAdminToken();
        IdentityZone zone = result.getIdentityZone();

        String groupName = generator.generate();
        String headerName = IdentityZoneSwitchingFilter.HEADER;
        String headerValue = zone.getId();

        ScimGroup group = new ScimGroup(null, groupName, null);

        MockHttpServletRequestBuilder create = post("/Groups").header(headerName, headerValue)
                .header("Authorization", "bearer " + zoneAdminToken).accept(APPLICATION_JSON)
                .contentType(APPLICATION_JSON).content(JsonUtils.writeValueAsString(group));

        group = JsonUtils.readValue(getMockMvc().perform(create).andExpect(status().isCreated()).andReturn()
                .getResponse().getContentAsString(), ScimGroup.class);

        MockHttpServletRequestBuilder update = put("/Groups/" + group.getId()).header(headerName, headerValue)
                .header("Authorization", "bearer " + zoneAdminToken).header("If-Match", group.getVersion())
                .accept(APPLICATION_JSON).contentType(APPLICATION_JSON)
                .content(JsonUtils.writeValueAsString(group));

        group = JsonUtils.readValue(getMockMvc().perform(update).andExpect(status().isOk()).andReturn()
                .getResponse().getContentAsString(), ScimGroup.class);

        MockHttpServletRequestBuilder get = get("/Groups/" + group.getId()).header(headerName, headerValue)
                .header("Authorization", "bearer " + zoneAdminToken).accept(APPLICATION_JSON);

        assertEquals(group, JsonUtils.readValue(
                getMockMvc().perform(get).andExpect(status().isOk()).andReturn().getResponse().getContentAsString(),
                ScimGroup.class));
    }

    //    @Test
    //    @Ignore //we only create DB once - so can no longer run
    //    public void testDBisDownDuringCreate() throws Exception {
    //        for (String s  : getWebApplicationContext().getEnvironment().getActiveProfiles()) {
    //            Assume.assumeFalse("Does not run during MySQL", "mysql".equals(s));
    //            Assume.assumeFalse("Does not run during PostgreSQL", "postgresql".equals(s));
    //        }
    //        String externalGroup = "cn=developers,ou=scopes,dc=test,dc=com";
    //        String displayName ="internal.read";
    //        DataSource ds = getWebApplicationContext().getBean(DataSource.class);
    //        new JdbcTemplate(ds).execute("SHUTDOWN");
    //        Method close = ds.getClass().getMethod("close");
    //        Assert.assertNotNull(close);
    //        close.invoke(ds);
    //        ResultActions result = createGroup(null, displayName, externalGroup);
    //        result.andExpect(status().isServiceUnavailable());
    //    }

    @Test
    public void getGroups_withScimReadTokens_returnsOkWithResults() throws Exception {
        String filterNarrow = "displayName eq \"clients.read\" or displayName eq \"clients.write\"";
        String filterWide = "displayName eq \"clients.read\" or displayName eq \"clients.write\" or displayName eq \"zones.read\" or displayName eq \"zones.write\"";

        MockHttpServletRequestBuilder get = get("/Groups").header("Authorization", "Bearer " + scimReadToken)
                .param("attributes", "displayName").param("filter", filterNarrow)
                .contentType(MediaType.APPLICATION_JSON).accept(APPLICATION_JSON);
        MvcResult mvcResult = getMockMvc().perform(get).andExpect(status().isOk()).andReturn();

        String body = mvcResult.getResponse().getContentAsString();
        SearchResults<ScimGroup> searchResults = JsonUtils.readValue(body, SearchResults.class);
        assertThat("Search results: " + body, searchResults.getResources(), hasSize(2));

        get = get("/Groups").header("Authorization", "Bearer " + scimReadUserToken)
                .param("attributes", "displayName").param("filter", filterNarrow)
                .contentType(MediaType.APPLICATION_JSON).accept(APPLICATION_JSON);
        mvcResult = getMockMvc().perform(get).andExpect(status().isOk()).andReturn();

        body = mvcResult.getResponse().getContentAsString();
        searchResults = JsonUtils.readValue(body, SearchResults.class);
        assertThat("Search results: " + body, searchResults.getResources(), hasSize(2));

        get = get("/Groups").header("Authorization", "Bearer " + scimReadToken)
                .contentType(MediaType.APPLICATION_JSON).param("filter", filterWide).accept(APPLICATION_JSON);
        mvcResult = getMockMvc().perform(get).andExpect(status().isOk()).andReturn();

        body = mvcResult.getResponse().getContentAsString();
        searchResults = JsonUtils.readValue(body, SearchResults.class);
        assertThat("Search results: " + body, searchResults.getResources(), hasSize(4));

        get = get("/Groups").header("Authorization", "Bearer " + scimReadUserToken)
                .contentType(MediaType.APPLICATION_JSON).param("filter", filterWide).accept(APPLICATION_JSON);
        mvcResult = getMockMvc().perform(get).andExpect(status().isOk()).andReturn();

        body = mvcResult.getResponse().getContentAsString();
        searchResults = JsonUtils.readValue(body, SearchResults.class);
        assertThat("Search results: " + body, searchResults.getResources(), hasSize(4));
    }

    @Test
    public void getGroupsInOtherZone_withZoneAdminToken_returnsOkWithResults() throws Exception {
        String subdomain = new RandomValueStringGenerator(8).generate();
        BaseClientDetails bootstrapClient = null;
        MockMvcUtils.IdentityZoneCreationResult result = utils().createOtherIdentityZoneAndReturnResult(subdomain,
                getMockMvc(), getWebApplicationContext(), bootstrapClient);

        ScimGroup group1 = new ScimGroup(null, "scim.whatever", result.getIdentityZone().getId());
        ScimGroup group2 = new ScimGroup(null, "another.group", result.getIdentityZone().getId());

        getMockMvc()
                .perform(post("/Groups")
                        .header(IdentityZoneSwitchingFilter.HEADER, result.getIdentityZone().getId())
                        .header("Authorization", "bearer " + result.getZoneAdminToken()).accept(APPLICATION_JSON)
                        .contentType(APPLICATION_JSON).content(JsonUtils.writeValueAsString(group1)))
                .andExpect(status().isCreated());

        getMockMvc()
                .perform(post("/Groups")
                        .header(IdentityZoneSwitchingFilter.HEADER, result.getIdentityZone().getId())
                        .header("Authorization", "bearer " + result.getZoneAdminToken()).accept(APPLICATION_JSON)
                        .contentType(APPLICATION_JSON).content(JsonUtils.writeValueAsString(group2)))
                .andExpect(status().isCreated());

        MockHttpServletRequestBuilder get = get("/Groups")
                .header("Authorization", "Bearer " + result.getZoneAdminToken())
                .header(IdentityZoneSwitchingFilter.HEADER, result.getIdentityZone().getId())
                .param("attributes", "displayName").param("filter", "displayName co \"scim\"")
                .contentType(MediaType.APPLICATION_JSON).accept(APPLICATION_JSON);
        MvcResult mvcResult = getMockMvc().perform(get).andExpect(status().isOk()).andReturn();

        SearchResults searchResults = JsonUtils.readValue(mvcResult.getResponse().getContentAsString(),
                SearchResults.class);
        assertThat(searchResults.getResources().size(), is(getSystemScopes("scim").size() + 1));

        get = get("/Groups").header("Authorization", "Bearer " + result.getZoneAdminToken())
                .header(IdentityZoneSwitchingFilter.HEADER, result.getIdentityZone().getId())
                .contentType(MediaType.APPLICATION_JSON).accept(APPLICATION_JSON);
        mvcResult = getMockMvc().perform(get).andExpect(status().isOk()).andReturn();

        searchResults = JsonUtils.readValue(mvcResult.getResponse().getContentAsString(), SearchResults.class);
        assertThat(searchResults.getResources().size(), is(getSystemScopes(null).size() + 2));
    }

    protected List<String> getSystemScopes(String containing) {
        List<String> systemScopes = ZoneManagementScopes.getSystemScopes();
        if (hasText(containing)) {
            return systemScopes.stream().filter(s -> s.contains(containing)).collect(Collectors.toList());
        } else {
            return systemScopes;
        }
    }

    @Test
    public void getGroupsInOtherZone_withZoneUserToken_returnsOkWithResults() throws Exception {
        String subdomain = new RandomValueStringGenerator(8).generate();
        BaseClientDetails bootstrapClient = null;
        MockMvcUtils.IdentityZoneCreationResult result = utils().createOtherIdentityZoneAndReturnResult(subdomain,
                getMockMvc(), getWebApplicationContext(), bootstrapClient);

        String zonedClientId = "zonedClientId";
        String zonedClientSecret = "zonedClientSecret";
        BaseClientDetails zonedClientDetails = (BaseClientDetails) utils().createClient(getMockMvc(),
                result.getZoneAdminToken(), zonedClientId, zonedClientSecret, Collections.singleton("oauth"),
                Arrays.asList("scim.read"), Arrays.asList("client_credentials", "password"), "scim.read", null,
                result.getIdentityZone());
        zonedClientDetails.setClientSecret(zonedClientSecret);

        ScimUser zoneUser = createUserAndAddToGroups(result.getIdentityZone(),
                new HashSet(Arrays.asList("scim.read")));

        String basicDigestHeaderValue = "Basic "
                + new String(Base64.encodeBase64((zonedClientId + ":" + zonedClientSecret).getBytes()));
        MockHttpServletRequestBuilder oauthTokenPost = post("/oauth/token")
                .with(new SetServerNameRequestPostProcessor(result.getIdentityZone().getSubdomain() + ".localhost"))
                .header("Authorization", basicDigestHeaderValue).param("grant_type", "password")
                .param("client_id", zonedClientId).param("username", zoneUser.getUserName())
                .param("password", "password").param("scope", "scim.read");
        MvcResult tokenResult = getMockMvc().perform(oauthTokenPost).andExpect(status().isOk()).andReturn();
        OAuthToken oauthToken = JsonUtils.readValue(tokenResult.getResponse().getContentAsString(),
                OAuthToken.class);
        String zoneUserToken = oauthToken.accessToken;

        MockHttpServletRequestBuilder get = get("/Groups")
                .with(new SetServerNameRequestPostProcessor(result.getIdentityZone().getSubdomain() + ".localhost"))
                .header("Authorization", "Bearer " + zoneUserToken)
                //                .header(IdentityZoneSwitchingFilter.HEADER, result.getIdentityZone().getId())
                .param("attributes", "displayName").param("filter", "displayName co \"scim\"")
                .contentType(MediaType.APPLICATION_JSON).accept(APPLICATION_JSON);
        MvcResult mvcResult = getMockMvc().perform(get).andExpect(status().isOk()).andReturn();

        SearchResults searchResults = JsonUtils.readValue(mvcResult.getResponse().getContentAsString(),
                SearchResults.class);
        assertThat(searchResults.getResources().size(), is(getSystemScopes("scim").size()));

        get = get("/Groups")
                .with(new SetServerNameRequestPostProcessor(result.getIdentityZone().getSubdomain() + ".localhost"))
                .header("Authorization", "Bearer " + zoneUserToken)
                //                .header(IdentityZoneSwitchingFilter.HEADER, result.getIdentityZone().getId())
                .contentType(MediaType.APPLICATION_JSON).accept(APPLICATION_JSON);
        mvcResult = getMockMvc().perform(get).andExpect(status().isOk()).andReturn();

        searchResults = JsonUtils.readValue(mvcResult.getResponse().getContentAsString(), SearchResults.class);
        assertThat(searchResults.getResources().size(), is(getSystemScopes(null).size()));
    }

    @Test
    public void testGetGroupsInvalidFilter() throws Exception {
        MockHttpServletRequestBuilder get = get("/Groups").header("Authorization", "Bearer " + scimReadToken)
                .contentType(MediaType.APPLICATION_JSON).accept(APPLICATION_JSON)
                .param("filter", "blabla eq \"test\"");

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

        get = get("/Groups").header("Authorization", "Bearer " + scimReadUserToken)
                .contentType(MediaType.APPLICATION_JSON).accept(APPLICATION_JSON)
                .param("filter", "blabla eq \"test\"");

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

    @Test
    public void testGetGroupsInvalidAttributes() throws Exception {
        String nonexistentAttribute = "displayBlaBla";

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

        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 testExternalGroupMembershipManagerNotNull() throws Exception {
        ScimGroupEndpoints sge = getWebApplicationContext().getBean(ScimGroupEndpoints.class);
        assertNotNull(sge.getExternalMembershipManager());
    }

    @Test
    public void testGetExternalGroups() throws Exception {
        checkGetExternalGroups();
    }

    @Test
    public void testCreateExternalGroupMapUsingName() throws Exception {
        String displayName = "internal.read";
        String externalGroup = "cn=java-developers,ou=scopes,dc=test,dc=com";
        ResultActions result = createGroup(null, displayName, externalGroup);
        result.andExpect(status().isCreated());

        //add the newly added list to our expected list, and check again.
        int previousSize = defaultExternalMembers.size();
        ArrayList<String> list = new ArrayList<>(defaultExternalMembers);
        list.add(displayName + "|" + externalGroup);
        defaultExternalMembers = list;
        assertEquals(previousSize + 1, defaultExternalMembers.size());
        checkGetExternalGroups();
    }

    @Test
    public void testCreateExternalGroupMapUsingNameAlreadyExists() throws Exception {
        String displayName = "internal.read";
        String externalGroup = "cn=developers,ou=scopes,dc=test,dc=com";
        ResultActions result = createGroup(null, displayName, externalGroup);
        //we don't throw in JdbcScimGroupExternalMembershipManager.java
        //result.andExpect(status().isConflict());
        result.andExpect(status().isCreated());
    }

    @Test
    public void testCreateExternalGroupMapNameDoesNotExists() throws Exception {
        String displayName = "internal.read" + "sdasdas";
        String externalGroup = "cn=developers,ou=scopes,dc=test,dc=com";
        ResultActions result = createGroup(null, displayName, externalGroup);
        result.andExpect(status().isNotFound());
    }

    @Test
    public void testCreateExternalGroupMapNameIsNull() throws Exception {
        String externalGroup = "cn=developers,ou=scopes,dc=test,dc=com";
        ResultActions result = createGroup(null, null, externalGroup);
        result.andExpect(status().isNotFound());
    }

    @Test
    public void testCreateExternalGroupMapUsingId() throws Exception {
        String displayName = "internal.read";
        String groupId = getGroupId(displayName);
        String externalGroup = "cn=java-developers,ou=scopes,dc=test,dc=com";

        ResultActions result = createGroup(groupId, null, externalGroup);
        result.andExpect(status().isCreated());

        //add the newly added list to our expected list, and check again.
        int previousSize = defaultExternalMembers.size();
        ArrayList<String> list = new ArrayList<>(defaultExternalMembers);
        list.add(displayName + "|" + externalGroup);
        defaultExternalMembers = list;
        assertEquals(previousSize + 1, defaultExternalMembers.size());
        checkGetExternalGroups();
    }

    @Test
    public void test_create_and_update_group_description() throws Exception {
        String name = new RandomValueStringGenerator().generate();
        ScimGroup group = new ScimGroup(name);
        group.setZoneId("some-other-zone");
        group.setDescription(name + "-description");

        String content = JsonUtils.writeValueAsString(group);
        MockHttpServletRequestBuilder action = MockMvcRequestBuilders.post("/Groups")
                .header("Authorization", "Bearer " + scimWriteToken).contentType(MediaType.APPLICATION_JSON)
                .accept(APPLICATION_JSON).content(content);

        ScimGroup newGroup = JsonUtils.readValue(getMockMvc().perform(action).andExpect(status().isCreated())
                .andReturn().getResponse().getContentAsString(), ScimGroup.class);
        assertNotNull(newGroup);
        assertNotNull(newGroup.getId());
        assertEquals(IdentityZone.getUaa().getId(), newGroup.getZoneId());
        assertEquals(group.getDisplayName(), newGroup.getDisplayName());
        assertEquals(group.getDescription(), newGroup.getDescription());

        group.setDescription(name + "-description-updated");
        newGroup.setDescription(group.getDescription());

        content = JsonUtils.writeValueAsString(newGroup);
        action = MockMvcRequestBuilders.put("/Groups/" + newGroup.getId())
                .header("Authorization", "Bearer " + scimWriteToken).header("If-Match", newGroup.getVersion())
                .contentType(MediaType.APPLICATION_JSON).accept(APPLICATION_JSON).content(content);

        newGroup = JsonUtils.readValue(getMockMvc().perform(action).andExpect(status().isOk()).andReturn()
                .getResponse().getContentAsString(), ScimGroup.class);

        assertNotNull(newGroup);
        assertNotNull(newGroup.getId());
        assertEquals(IdentityZone.getUaa().getId(), newGroup.getZoneId());
        assertEquals(group.getDisplayName(), newGroup.getDisplayName());
        assertEquals(group.getDescription(), newGroup.getDescription());

    }

    protected ResultActions createGroup(String id, String name, String externalName) throws Exception {
        ScimGroupExternalMember em = new ScimGroupExternalMember();
        if (id != null)
            em.setGroupId(id);
        if (externalName != null)
            em.setExternalGroup(externalName);
        if (name != null)
            em.setDisplayName(name);
        String content = JsonUtils.writeValueAsString(em);
        MockHttpServletRequestBuilder post = MockMvcRequestBuilders.post("/Groups/External")
                .header("Authorization", "Bearer " + scimWriteToken).contentType(MediaType.APPLICATION_JSON)
                .accept(APPLICATION_JSON).content(content);

        ResultActions result = getMockMvc().perform(post);
        return result;
    }

    @Test
    public void testDeleteExternalGroupMapUsingNameDeprecatedAPI() throws Exception {
        String displayName = "internal.read";
        String externalGroup = "cn=developers,ou=scopes,dc=test,dc=com";
        ScimGroupExternalMember em = new ScimGroupExternalMember();
        em.setDisplayName(displayName);
        em.setExternalGroup(externalGroup);

        MockHttpServletRequestBuilder delete = MockMvcRequestBuilders
                .delete("/Groups/External/" + displayName + "/" + externalGroup)
                .header("Authorization", "Bearer " + scimWriteToken).accept(APPLICATION_JSON);

        ResultActions result = getMockMvc().perform(delete);
        result.andExpect(status().isOk());

        //remove the deleted map from our expected list, and check again.
        int previousSize = defaultExternalMembers.size();
        ArrayList<String> list = new ArrayList<>(defaultExternalMembers);
        assertTrue(list.remove(displayName + "|" + externalGroup));
        defaultExternalMembers = list;
        assertEquals(previousSize - 1, defaultExternalMembers.size());
        checkGetExternalGroups();
    }

    @Test
    public void testDeleteExternalGroupMapUsingName() throws Exception {
        String displayName = "internal.read";
        String externalGroup = "cn=developers,ou=scopes,dc=test,dc=com";
        ScimGroupExternalMember em = new ScimGroupExternalMember();
        em.setDisplayName(displayName);
        em.setExternalGroup(externalGroup);

        MockHttpServletRequestBuilder post = MockMvcRequestBuilders
                .delete("/Groups/External/displayName/" + displayName + "/externalGroup/" + externalGroup
                        + "/origin/ldap")
                .header("Authorization", "Bearer " + scimWriteToken).accept(APPLICATION_JSON);

        ResultActions result = getMockMvc().perform(post);
        result.andExpect(status().isOk());

        //remove the deleted map from our expected list, and check again.
        int previousSize = defaultExternalMembers.size();
        ArrayList<String> list = new ArrayList<>(defaultExternalMembers);
        assertTrue(list.remove(displayName + "|" + externalGroup));
        defaultExternalMembers = list;
        assertEquals(previousSize - 1, defaultExternalMembers.size());
        checkGetExternalGroups();
    }

    @Test
    public void testDeleteExternalGroupMapUsingNonExistentName() throws Exception {
        String displayName = "internal.read.nonexistent";
        String externalGroup = "cn=developers,ou=scopes,dc=test,dc=com";
        ScimGroupExternalMember em = new ScimGroupExternalMember();
        em.setDisplayName(displayName);
        em.setExternalGroup(externalGroup);

        MockHttpServletRequestBuilder post = MockMvcRequestBuilders
                .delete("/Groups/External/displayName/" + displayName + "/externalGroup/" + externalGroup
                        + "/origin/ldap")
                .header("Authorization", "Bearer " + scimWriteToken).accept(APPLICATION_JSON);

        ResultActions result = getMockMvc().perform(post);
        result.andExpect(status().isNotFound());
    }

    @Test
    public void testDeleteExternalGroupMapUsingIdDeprecatedAPI() throws Exception {
        String displayName = "internal.read";
        String externalGroup = "cn=developers,ou=scopes,dc=test,dc=com";
        String groupId = getGroupId(displayName);

        MockHttpServletRequestBuilder post = MockMvcRequestBuilders
                .delete("/Groups/External/id/" + groupId + "/" + externalGroup)
                .header("Authorization", "Bearer " + scimWriteToken).accept(APPLICATION_JSON);

        ResultActions result = getMockMvc().perform(post);
        result.andExpect(status().isOk());

        //remove the deleted map from our expected list, and check again.
        int previousSize = defaultExternalMembers.size();
        ArrayList<String> list = new ArrayList<>(defaultExternalMembers);
        assertTrue(list.remove(displayName + "|" + externalGroup));
        defaultExternalMembers = list;
        assertEquals(previousSize - 1, defaultExternalMembers.size());
        checkGetExternalGroups();
    }

    @Test
    public void testDeleteExternalGroupMapUsingId() throws Exception {
        String displayName = "internal.read";
        String externalGroup = "cn=developers,ou=scopes,dc=test,dc=com";
        String origin = LDAP;
        String groupId = getGroupId(displayName);

        MockHttpServletRequestBuilder post = MockMvcRequestBuilders
                .delete("/Groups/External/groupId/" + groupId + "/externalGroup/" + externalGroup + "/origin/uaa")
                .header("Authorization", "Bearer " + scimWriteToken).accept(APPLICATION_JSON);

        ResultActions result = getMockMvc().perform(post);
        result.andExpect(status().isNotFound());

        post = MockMvcRequestBuilders.delete(
                "/Groups/External/groupId/" + groupId + "/externalGroup/" + externalGroup + "/origin/" + origin)
                .header("Authorization", "Bearer " + scimWriteToken).accept(APPLICATION_JSON);

        result = getMockMvc().perform(post);
        result.andExpect(status().isOk());

        //remove the deleted map from our expected list, and check again.
        int previousSize = defaultExternalMembers.size();
        ArrayList<String> list = new ArrayList<>(defaultExternalMembers);
        assertTrue(list.remove(displayName + "|" + externalGroup));
        defaultExternalMembers = list;
        assertEquals(previousSize - 1, defaultExternalMembers.size());
        checkGetExternalGroups();
    }

    @Test
    public void testDeleteExternalGroupMapUsingNonExistentId() throws Exception {
        String externalGroup = "cn=developers,ou=scopes,dc=test,dc=com";
        String groupId = "non-existent";

        MockHttpServletRequestBuilder post = MockMvcRequestBuilders
                .delete("/Groups/External/groupId/" + groupId + "/externalGroup/" + externalGroup + "/origin/ldap")
                .header("Authorization", "Bearer " + scimWriteToken).accept(APPLICATION_JSON);

        ResultActions result = getMockMvc().perform(post);
        result.andExpect(status().isNotFound());
    }

    @Test
    public void testDeleteExternalGroupMapUsingReadToken() throws Exception {
        String displayName = "internal.read";
        String externalGroup = "cn=developers,ou=scopes,dc=test,dc=com";
        String groupId = getGroupId(displayName);

        MockHttpServletRequestBuilder post = MockMvcRequestBuilders
                .delete("/Groups/External/id/" + groupId + "/" + externalGroup + "/origin/ldap")
                .header("Authorization", "Bearer " + scimReadToken).accept(APPLICATION_JSON);

        ResultActions result = getMockMvc().perform(post);
        result.andExpect(status().isForbidden());

        checkGetExternalGroups();
    }

    @Test
    public void testGetExternalGroupsFilter() throws Exception {
        checkGetExternalGroupsFilter("displayName", "internal.");
        checkGetExternalGroupsFilter("externalGroup", "o=springsource,o=org");
        checkGetExternalGroupsFilter("groupId", databaseExternalMembers.get(2).getGroupId());

    }

    @Test
    public void get_group_membership() throws Exception {
        String groupId = getGroupId("scim.read");
        MockHttpServletRequestBuilder get = get("/Groups/" + groupId + "/members/" + scimUser.getId())
                .header("Authorization", "Bearer " + scimReadToken);
        MvcResult mvcResult = getMockMvc().perform(get).andExpect(status().isOk()).andReturn();
        ScimGroupMember scimGroupMember = JsonUtils.readValue(mvcResult.getResponse().getContentAsString(),
                ScimGroupMember.class);
        assertNotNull(scimGroupMember);
        assertEquals(scimUser.getId(), scimGroupMember.getMemberId());
    }

    @Test
    public void get_group_membership_user_not_member_of_group() throws Exception {
        String groupId = getGroupId("scim.read");
        MockHttpServletRequestBuilder get = get("/Groups/" + groupId + "/members/id-of-random-user")
                .header("Authorization", "Bearer " + scimReadToken);
        getMockMvc().perform(get).andExpect(status().isNotFound()).andReturn();
    }

    @Test
    public void get_group_membership_nonexistent_group() throws Exception {
        MockHttpServletRequestBuilder get = get("/Groups/nonexistent-group-id/members/" + scimUser.getId())
                .header("Authorization", "Bearer " + scimReadToken);
        getMockMvc().perform(get).andExpect(status().isNotFound()).andReturn();
    }

    @Test
    public void get_group_membership_nonexistent_user() throws Exception {
        String groupId = getGroupId("scim.read");
        MockHttpServletRequestBuilder get = get("/Groups/" + groupId + "/members/non-existent-user")
                .header("Authorization", "Bearer " + scimReadToken);
        getMockMvc().perform(get).andExpect(status().isNotFound()).andReturn();
    }

    @Test
    public void get_all_group_memberships() throws Exception {
        String groupName = "random." + new RandomValueStringGenerator().generate();
        ScimGroup group = new ScimGroup(groupName);
        group = MockMvcUtils.createGroup(getMockMvc(), scimWriteToken, group);
        String groupId = getGroupId(groupName);
        assertEquals(group.getId(), groupId);

        scimUser = createUserAndAddToGroups(IdentityZone.getUaa(), new HashSet(Arrays.asList(groupName)));

        ScimUser secondUser = createUserAndAddToGroups(IdentityZone.getUaa(), Collections.singleton(groupName));
        ScimGroup innerGroup = createGroupWithinGroups(IdentityZone.getUaa(), Collections.singleton(groupName));

        MockHttpServletRequestBuilder get = get("/Groups/" + groupId + "/members/").header("Authorization",
                "Bearer " + scimReadToken);
        MvcResult mvcResult = getMockMvc().perform(get).andExpect(status().isOk()).andReturn();
        String responseContent = mvcResult.getResponse().getContentAsString();
        List<Object> listMembers = JsonUtils.readValue(responseContent, new TypeReference<List<Object>>() {
        });
        Set<String> retrievedMembers = listMembers.stream().map(o -> JsonUtils.writeValueAsString(o))
                .collect(Collectors.toSet());

        Matcher<Iterable<? extends String>> containsExpectedMembers = containsInAnyOrder(
                JsonUtils.writeValueAsString(new ScimGroupMember(innerGroup.getId(), ScimGroupMember.Type.GROUP,
                        Arrays.asList(ScimGroupMember.Role.MEMBER))),
                JsonUtils.writeValueAsString(new ScimGroupMember(secondUser.getId(), ScimGroupMember.Type.USER,
                        Arrays.asList(ScimGroupMember.Role.MEMBER))),
                JsonUtils.writeValueAsString(new ScimGroupMember(scimUser.getId(), ScimGroupMember.Type.USER,
                        Arrays.asList(ScimGroupMember.Role.MEMBER))));

        Assert.assertThat(retrievedMembers, containsExpectedMembers);
    }

    @Test
    public void get_group_memberships_with_entities() throws Exception {

        String groupName = "random." + new RandomValueStringGenerator().generate();
        ScimGroup group = new ScimGroup(groupName);
        group = MockMvcUtils.createGroup(getMockMvc(), scimWriteToken, group);
        String groupId = getGroupId(groupName);
        assertEquals(group.getId(), groupId);

        scimUser = createUserAndAddToGroups(IdentityZone.getUaa(), new HashSet(Arrays.asList(groupName)));

        ScimUser secondUser = createUserAndAddToGroups(IdentityZone.getUaa(), Collections.singleton(groupName));
        ScimGroup innerGroup = createGroupWithinGroups(IdentityZone.getUaa(), Collections.singleton(groupName));

        MockHttpServletRequestBuilder get = get("/Groups/" + groupId + "/members/")
                .header("Authorization", "Bearer " + scimReadToken).param("returnEntities", "true");
        MvcResult mvcResult = getMockMvc().perform(get).andExpect(status().isOk()).andReturn();
        String responseContent = mvcResult.getResponse().getContentAsString();
        List<Object> listMembers = JsonUtils.readValue(responseContent, new TypeReference<List<Object>>() {
        });
        Set<String> retrievedMembers = listMembers.stream().map(o -> JsonUtils.writeValueAsString(o))
                .collect(Collectors.toSet());

        Matcher<Iterable<? extends String>> containsExpectedMembers = containsInAnyOrder(
                JsonUtils.writeValueAsString(new ScimGroupMember(innerGroup)),
                JsonUtils.writeValueAsString(new ScimGroupMember(secondUser)),
                JsonUtils.writeValueAsString(new ScimGroupMember(scimUser)));

        Assert.assertThat(retrievedMembers, containsExpectedMembers);
    }

    @Test
    public void get_filtered_group_memberships() throws Exception {
        String groupName = "random." + new RandomValueStringGenerator().generate();
        ScimGroup group = new ScimGroup(groupName);
        group = MockMvcUtils.createGroup(getMockMvc(), scimWriteToken, group);
        String groupId = getGroupId(groupName);
        assertEquals(group.getId(), groupId);

        scimUser = createUserAndAddToGroups(IdentityZone.getUaa(), new HashSet(Arrays.asList(groupName)));

        ScimUser secondUser = createUserAndAddToGroups(IdentityZone.getUaa(), Collections.singleton(groupName));
        ScimGroup innerGroup = createGroupWithinGroups(IdentityZone.getUaa(), Collections.singleton(groupName));

        MockHttpServletRequestBuilder get = get("/Groups/" + groupId + "/members/")
                .header("Authorization", "Bearer " + scimReadToken).param("filter", "member_type eq 'GROUP'");
        MvcResult mvcResult = getMockMvc().perform(get).andExpect(status().isOk()).andReturn();
        String responseContent = mvcResult.getResponse().getContentAsString();
        List<Object> listMembers = JsonUtils.readValue(responseContent, new TypeReference<List<Object>>() {
        });
        Set<String> retrievedMembers = listMembers.stream().map(o -> JsonUtils.writeValueAsString(o))
                .collect(Collectors.toSet());

        Matcher<Iterable<? extends String>> containsExpectedMembers = containsInAnyOrder(
                JsonUtils.writeValueAsString(new ScimGroupMember(innerGroup.getId(), ScimGroupMember.Type.GROUP,
                        Arrays.asList(ScimGroupMember.Role.MEMBER))));

        Assert.assertThat(retrievedMembers, containsExpectedMembers);
    }

    @Test
    public void get_group_memberships_for_nonexistent_group() throws Exception {
        MockHttpServletRequestBuilder get = get("/Groups/nonexistent-group-id/members/").header("Authorization",
                "Bearer " + scimReadToken);
        getMockMvc().perform(get).andExpect(status().isNotFound()).andReturn();
    }

    @Test
    public void add_member_to_group() throws Exception {
        ScimUser user = createUserAndAddToGroups(IdentityZone.getUaa(), Collections.EMPTY_SET);
        String groupId = getGroupId("scim.read");
        ScimGroupMember scimGroupMember = new ScimGroupMember(user.getId(), ScimGroupMember.Type.USER,
                Arrays.asList(ScimGroupMember.Role.MEMBER, ScimGroupMember.Role.READER));
        MockHttpServletRequestBuilder post = post("/Groups/" + groupId + "/members")
                .header("Authorization", "Bearer " + scimWriteToken).header("Content-Type", APPLICATION_JSON_VALUE)
                .content(JsonUtils.writeValueAsString(scimGroupMember));
        String responseBody = getMockMvc().perform(post).andExpect(status().isCreated()).andReturn().getResponse()
                .getContentAsString();
        assertEquals(JsonUtils.writeValueAsString(scimGroupMember), responseBody);
    }

    @Test
    public void add_member_to_group_twice() throws Exception {
        ScimUser user = createUserAndAddToGroups(IdentityZone.getUaa(), Collections.EMPTY_SET);
        String groupId = getGroupId("scim.read");
        ScimGroupMember scimGroupMember = new ScimGroupMember(user.getId(), ScimGroupMember.Type.USER,
                Arrays.asList(ScimGroupMember.Role.MEMBER, ScimGroupMember.Role.READER));
        getMockMvc().perform(post("/Groups/" + groupId + "/members")
                .header("Authorization", "Bearer " + scimWriteToken).header("Content-Type", APPLICATION_JSON_VALUE)
                .content(JsonUtils.writeValueAsString(scimGroupMember))).andExpect(status().isCreated());

        scimGroupMember.setRoles(Arrays.asList(ScimGroupMember.Role.WRITER));
        getMockMvc().perform(post("/Groups/" + groupId + "/members")
                .header("Authorization", "Bearer " + scimWriteToken).header("Content-Type", APPLICATION_JSON_VALUE)
                .content(JsonUtils.writeValueAsString(scimGroupMember))).andExpect(status().isConflict());
    }

    @Test
    public void update_member_in_group() throws Exception {
        ScimUser user = createUserAndAddToGroups(IdentityZone.getUaa(), Collections.singleton("scim.read"));
        String groupId = getGroupId("scim.read");
        ScimGroupMember scimGroupMember = new ScimGroupMember(user.getId(), ScimGroupMember.Type.USER,
                Arrays.asList(ScimGroupMember.Role.MEMBER, ScimGroupMember.Role.READER));

        scimGroupMember.setRoles(Arrays.asList(ScimGroupMember.Role.WRITER));
        String updatedMember = JsonUtils.writeValueAsString(scimGroupMember);
        getMockMvc()
                .perform(put("/Groups/" + groupId + "/members").header("Authorization", "Bearer " + scimWriteToken)
                        .header("Content-Type", APPLICATION_JSON_VALUE).content(updatedMember))
                .andExpect(status().isOk());
        assertNotNull(updatedMember);

        MockHttpServletRequestBuilder get = get("/Groups/" + groupId + "/members/" + scimGroupMember.getMemberId())
                .header("Authorization", "Bearer " + scimReadToken);
        MvcResult mvcResult = getMockMvc().perform(get).andExpect(status().isOk()).andReturn();
        String getResponse = mvcResult.getResponse().getContentAsString();
        assertEquals(updatedMember, getResponse);
    }

    @Test
    public void update_member_in_nonexistent_group() throws Exception {
        ScimGroupMember scimGroupMember = new ScimGroupMember(scimUser.getId(), ScimGroupMember.Type.USER,
                Arrays.asList(ScimGroupMember.Role.MEMBER, ScimGroupMember.Role.READER));

        getMockMvc().perform(put("/Groups/nonexistent-group-id/members")
                .header("Authorization", "Bearer " + scimWriteToken).header("Content-Type", APPLICATION_JSON_VALUE)
                .content(JsonUtils.writeValueAsString(scimGroupMember))).andExpect(status().isNotFound());
    }

    @Test
    public void update_member_does_not_exist_in_group() throws Exception {
        ScimGroupMember scimGroupMember = new ScimGroupMember(scimUser.getId(), ScimGroupMember.Type.USER,
                Arrays.asList(ScimGroupMember.Role.MEMBER, ScimGroupMember.Role.READER));

        String groupId = getGroupId("acme");
        getMockMvc().perform(put("/Groups/" + groupId + "/members")
                .header("Authorization", "Bearer " + scimWriteToken).header("Content-Type", APPLICATION_JSON_VALUE)
                .content(JsonUtils.writeValueAsString(scimGroupMember))).andExpect(status().isNotFound());
    }

    @Test
    public void update_nonexistent_user() throws Exception {
        ScimGroupMember scimGroupMember = new ScimGroupMember("non-existent-user", ScimGroupMember.Type.USER,
                Arrays.asList(ScimGroupMember.Role.MEMBER, ScimGroupMember.Role.READER));

        String groupId = getGroupId("scim.read");
        getMockMvc().perform(put("/Groups/" + groupId + "/members")
                .header("Authorization", "Bearer " + scimWriteToken).header("Content-Type", APPLICATION_JSON_VALUE)
                .content(JsonUtils.writeValueAsString(scimGroupMember))).andExpect(status().isNotFound());
    }

    @Test
    public void delete_member_from_group() throws Exception {
        ScimUser user = createUserAndAddToGroups(IdentityZone.getUaa(), Collections.singleton("scim.read"));
        String groupId = getGroupId("scim.read");

        String deleteResponseBody = getMockMvc()
                .perform(delete("/Groups/" + groupId + "/members/" + user.getId())
                        .header("Authorization", "Bearer " + scimWriteToken)
                        .header("Content-Type", APPLICATION_JSON_VALUE))
                .andExpect(status().isOk()).andReturn().getResponse().getContentAsString();

        ScimGroupMember deletedMember = JsonUtils.readValue(deleteResponseBody, ScimGroupMember.class);

        assertEquals(user.getId(), deletedMember.getMemberId());
    }

    @Test
    public void delete_member_from_nonexistent_group() throws Exception {
        ScimUser user = createUserAndAddToGroups(IdentityZone.getUaa(), Collections.singleton("scim.read"));

        getMockMvc().perform(delete("/Groups/nonexistent-group/members/" + user.getId())
                .header("Authorization", "Bearer " + scimWriteToken).header("Content-Type", APPLICATION_JSON_VALUE))
                .andExpect(status().isNotFound());
    }

    @Test
    public void delete_user_not_member_of_group() throws Exception {
        String groupId = getGroupId("acme");
        getMockMvc().perform(delete("/Groups/" + groupId + "/members/" + scimUser.getId())
                .header("Authorization", "Bearer " + scimWriteToken).header("Content-Type", APPLICATION_JSON_VALUE))
                .andExpect(status().isNotFound());
    }

    @Test
    public void delete_nonexistent_user() throws Exception {
        getMockMvc().perform(delete("/Groups/nonexistent-group/members/non-existent-user")
                .header("Authorization", "Bearer " + scimWriteToken).header("Content-Type", APPLICATION_JSON_VALUE))
                .andExpect(status().isNotFound());
    }

    @Test
    public void add_member_to_nonexistent_group() throws Exception {
        ScimUser user = createUserAndAddToGroups(IdentityZone.getUaa(), Collections.EMPTY_SET);
        ScimGroupMember scimGroupMember = new ScimGroupMember(user.getId(), ScimGroupMember.Type.USER,
                Arrays.asList(ScimGroupMember.Role.MEMBER, ScimGroupMember.Role.READER));
        MockHttpServletRequestBuilder post = post("/Groups/nonexistent-group-id/members")
                .header("Authorization", "Bearer " + scimWriteToken).header("Content-Type", APPLICATION_JSON_VALUE)
                .content(JsonUtils.writeValueAsString(scimGroupMember));
        getMockMvc().perform(post).andExpect(status().isNotFound());
    }

    @Test
    public void add_nonexistent_user_to_group() throws Exception {
        String groupId = getGroupId("scim.read");
        ScimGroupMember scimGroupMember = new ScimGroupMember("random-user-id", ScimGroupMember.Type.USER,
                Arrays.asList(ScimGroupMember.Role.MEMBER, ScimGroupMember.Role.READER));
        MockHttpServletRequestBuilder post = post("/Groups/" + groupId + "/members")
                .header("Authorization", "Bearer " + scimWriteToken).header("Content-Type", APPLICATION_JSON_VALUE)
                .content(JsonUtils.writeValueAsString(scimGroupMember));
        getMockMvc().perform(post).andExpect(status().isNotFound());
    }

    protected void checkGetExternalGroupsFilter(String fieldName, String fieldValue) throws Exception {
        MockHttpServletRequestBuilder get = get("/Groups/External")
                .param("filter", fieldName + " co \"" + fieldValue + "\"")
                .header("Authorization", "Bearer " + scimReadToken).accept(APPLICATION_JSON);

        ResultActions result = getMockMvc().perform(get);
        result.andExpect(status().isOk());
        String content = result.andReturn().getResponse().getContentAsString();
        SearchResults<ScimGroupExternalMember> members;

        Map<String, Object> map = JsonUtils.readValue(content, Map.class);
        List<Map<String, String>> resources = (List<Map<String, String>>) map.get("resources");
        int startIndex = Integer.parseInt(map.get("startIndex").toString());
        int itemsPerPage = Integer.parseInt(map.get("itemsPerPage").toString());
        int totalResults = Integer.parseInt(map.get("totalResults").toString());
        List<ScimGroupExternalMember> memberList = new ArrayList<>();
        for (Map<String, String> m : resources) {
            ScimGroupExternalMember sgm = new ScimGroupExternalMember();
            sgm.setGroupId(m.get("groupId"));
            sgm.setDisplayName(m.get("displayName"));
            sgm.setExternalGroup(m.get("externalGroup"));
            sgm.setOrigin(m.get("origin"));
            memberList.add(sgm);
        }
        members = new SearchResults<>((List<String>) map.get("schemas"), memberList, startIndex, itemsPerPage,
                totalResults);
        assertNotNull(members);

        List<ScimGroupExternalMember> expected = new ArrayList<>();
        for (ScimGroupExternalMember m : databaseExternalMembers) {
            switch (fieldName) {
            case "displayName": {
                if (m.getDisplayName().startsWith(fieldValue)) {
                    expected.add(m);
                }
                break;
            }
            case "externalGroup": {
                if (m.getExternalGroup().contains(fieldValue)) {
                    expected.add(m);
                }
                break;
            }
            case "groupId": {
                if (m.getGroupId().contains(fieldValue)) {
                    expected.add(m);
                }
                break;
            }
            }
        }

        assertEquals(expected.size(), members.getResources().size());
        validateDbMembers(expected, members.getResources());
    }

    @Test
    public void testGetExternalGroupsPagination() throws Exception {
        checkGetExternalGroupsPagination(1);
        checkGetExternalGroupsPagination(2);
        checkGetExternalGroupsPagination(3);
        checkGetExternalGroupsPagination(4);
        checkGetExternalGroupsPagination(5);
        checkGetExternalGroupsPagination(6);
        checkGetExternalGroupsPagination(100);
    }

    protected void checkGetExternalGroupsPagination(int pageSize) throws Exception {
        List<SearchResults<ScimGroupExternalMember>> pages = new ArrayList<>();

        for (int start = 1; start <= databaseExternalMembers.size(); start += pageSize) {
            MockHttpServletRequestBuilder get = get("/Groups/External").param("startIndex", String.valueOf(start))
                    .param("count", String.valueOf(pageSize)).header("Authorization", "Bearer " + scimReadToken)
                    .accept(APPLICATION_JSON);

            ResultActions result = getMockMvc().perform(get);
            result.andExpect(status().isOk());
            String content = result.andReturn().getResponse().getContentAsString();
            SearchResults<ScimGroupExternalMember> page = null;

            Map<String, Object> map = JsonUtils.readValue(content, Map.class);
            List<Map<String, String>> resources = (List<Map<String, String>>) map.get("resources");
            int startIndex = Integer.parseInt(map.get("startIndex").toString());
            int itemsPerPage = Integer.parseInt(map.get("itemsPerPage").toString());
            int totalResults = Integer.parseInt(map.get("totalResults").toString());
            List<ScimGroupExternalMember> memberList = new ArrayList<>();
            for (Map<String, String> m : resources) {
                ScimGroupExternalMember sgm = new ScimGroupExternalMember();
                sgm.setGroupId(m.get("groupId"));
                sgm.setDisplayName(m.get("displayName"));
                sgm.setExternalGroup(m.get("externalGroup"));
                sgm.setOrigin(m.get("origin"));
                memberList.add(sgm);
            }
            page = new SearchResults<>((List<String>) map.get("schemas"), memberList, startIndex, itemsPerPage,
                    totalResults);
            pages.add(page);
        }

        List<ScimGroupExternalMember> members = pages.stream().flatMap(p -> p.getResources().stream())
                .collect(Collectors.toList());

        validateDbMembers(databaseExternalMembers, members);
    }

    protected void checkGetExternalGroups() throws Exception {
        String path = "/Groups/External";
        checkGetExternalGroups(path);
        path = "/Groups/External";
        checkGetExternalGroups(path);
    }

    protected void checkGetExternalGroups(String path) throws Exception {
        MockHttpServletRequestBuilder get = get(path).header("Authorization", "Bearer " + scimReadToken)
                .accept(APPLICATION_JSON);

        ResultActions result = getMockMvc().perform(get);
        result.andExpect(status().isOk());
        String content = result.andReturn().getResponse().getContentAsString();
        SearchResults<ScimGroupExternalMember> members = null;

        Map<String, Object> map = JsonUtils.readValue(content, Map.class);
        List<Map<String, String>> resources = (List<Map<String, String>>) map.get("resources");
        int startIndex = Integer.parseInt(map.get("startIndex").toString());
        int itemsPerPage = Integer.parseInt(map.get("itemsPerPage").toString());
        int totalResults = Integer.parseInt(map.get("totalResults").toString());
        List<ScimGroupExternalMember> memberList = new ArrayList<>();
        for (Map<String, String> m : resources) {
            ScimGroupExternalMember sgm = new ScimGroupExternalMember();
            sgm.setGroupId(m.get("groupId"));
            sgm.setDisplayName(m.get("displayName"));
            sgm.setExternalGroup(m.get("externalGroup"));
            sgm.setOrigin(m.get("origin"));
            memberList.add(sgm);
        }
        members = new SearchResults<>((List<String>) map.get("schemas"), memberList, startIndex, itemsPerPage,
                totalResults);
        assertNotNull(members);
        assertEquals(defaultExternalMembers.size(), members.getResources().size());
        validateMembers(defaultExternalMembers, members.getResources());
    }

    protected String getGroupId(String displayName) throws Exception {
        JdbcScimGroupProvisioning gp = (JdbcScimGroupProvisioning) getWebApplicationContext()
                .getBean("scimGroupProvisioning");
        List<ScimGroup> result = gp.query("displayName eq \"" + displayName + "\"");
        if (result == null || result.size() == 0) {
            throw new NullPointerException("Group not found:" + displayName);
        }
        if (result.size() > 1) {
            throw new IllegalStateException("Group name should be unique:" + displayName);
        }
        return result.get(0).getId();
    }

    protected void validateMembers(List<String> expected, Collection<ScimGroupExternalMember> actual) {
        List<ScimGroupExternalMember> members = new ArrayList<>();
        for (String s : expected) {
            String[] data = s.split("\\|");
            assertNotNull(data);
            assertEquals(2, data.length);
            String displayName = data[0];
            String externalId = data[1];
            ScimGroupExternalMember mbr = new ScimGroupExternalMember("N/A", externalId);
            mbr.setDisplayName(displayName);
            mbr.setOrigin(OriginKeys.LDAP);
            members.add(mbr);
        }
        validateDbMembers(members, actual);
    }

    protected void validateDbMembers(Collection<ScimGroupExternalMember> expected,
            Collection<ScimGroupExternalMember> actual) {
        for (ScimGroupExternalMember s : expected) {
            final String displayName = s.getDisplayName();
            final String externalId = s.getExternalGroup();
            final String origin = s.getOrigin();
            boolean found = false;
            for (ScimGroupExternalMember m : actual) {
                assertNotNull("Display name can not be null", m.getDisplayName());
                assertNotNull("External ID can not be null", m.getExternalGroup());
                if (m.getDisplayName().equals(displayName) && m.getExternalGroup().equals(externalId)
                        && m.getOrigin().equals(origin)) {
                    found = true;
                    break;
                }
            }
            assertTrue("Did not find expected external group mapping:" + s, found);
            assertEquals("The result set must contain exactly as many items as expected", expected.size(),
                    actual.size());
        }
    }

    private ScimUser createUserAndAddToGroups(IdentityZone zone, Set<String> groupNames) throws Exception {
        if (zone == null) {
            zone = IdentityZone.getUaa();
        }
        ScimUserProvisioning usersRepository = getWebApplicationContext().getBean(ScimUserProvisioning.class);
        ScimGroupProvisioning groupRepository = getWebApplicationContext().getBean(ScimGroupProvisioning.class);
        String email = "otheruser@" + generator.generate().toLowerCase() + ".com";
        ScimUser user = new ScimUser(null, email, "Other", "User");
        user.addEmail(email);
        user.setVerified(true);
        IdentityZone originalZone = IdentityZoneHolder.get();
        try {
            if (zone != null) {
                IdentityZoneHolder.set(zone);
            }
            user = usersRepository.createUser(user, "password");
            ephemeralResources.add(new String[] { user.getId(), "USER" });

            Collection<ScimUser.Group> scimUserGroups = new LinkedList<>();
            for (String groupName : groupNames) {
                List<ScimGroup> scimGroups = groupRepository.query("displayName eq \"" + groupName + "\"");
                ScimUser.Group scimUserGroup;
                ScimGroup group;
                if (scimGroups == null || scimGroups.isEmpty()) {
                    group = new ScimGroup(null, groupName, IdentityZoneHolder.get().getId());
                    group = groupRepository.create(group);
                    scimUserGroup = new ScimUser.Group(group.getId(), groupName);
                } else {
                    group = scimGroups.get(0);
                    scimUserGroup = new ScimUser.Group(scimGroups.get(0).getId(), groupName);
                }
                scimUserGroups.add(scimUserGroup);
                ScimGroupMembershipManager scimGroupMembershipManager = getWebApplicationContext()
                        .getBean(ScimGroupMembershipManager.class);
                ScimGroupMember member = new ScimGroupMember(user.getId(), ScimGroupMember.Type.USER,
                        Arrays.asList(ScimGroupMember.Role.READER));
                try {
                    scimGroupMembershipManager.addMember(group.getId(), member);
                } catch (MemberAlreadyExistsException x) {
                }
            }
        } finally {
            IdentityZoneHolder.set(originalZone);
        }
        return user;
    }

    private ScimGroup createGroupWithinGroups(IdentityZone zone, Set<String> groupNames) throws Exception {
        if (zone == null) {
            zone = IdentityZone.getUaa();
        }
        ScimGroupProvisioning groupRepository = getWebApplicationContext().getBean(ScimGroupProvisioning.class);
        ScimGroup newGroup = new ScimGroup(null, generator.generate(), zone.getId());
        IdentityZone originalZone = IdentityZoneHolder.get();
        try {
            if (zone != null) {
                IdentityZoneHolder.set(zone);
            }
            newGroup = groupRepository.create(newGroup);
            ephemeralResources.add(new String[] { newGroup.getId(), "GROUP" });

            Collection<ScimUser.Group> scimUserGroups = new LinkedList<>();
            for (String groupName : groupNames) {
                List<ScimGroup> scimGroups = groupRepository.query("displayName eq \"" + groupName + "\"");
                ScimUser.Group scimUserGroup;
                ScimGroup group;
                if (scimGroups == null || scimGroups.isEmpty()) {
                    group = new ScimGroup(null, groupName, IdentityZoneHolder.get().getId());
                    group = groupRepository.create(group);
                    scimUserGroup = new ScimUser.Group(group.getId(), groupName);
                } else {
                    group = scimGroups.get(0);
                    scimUserGroup = new ScimUser.Group(scimGroups.get(0).getId(), groupName);
                }
                scimUserGroups.add(scimUserGroup);
                ScimGroupMembershipManager scimGroupMembershipManager = getWebApplicationContext()
                        .getBean(ScimGroupMembershipManager.class);
                ScimGroupMember member = new ScimGroupMember(newGroup.getId(), ScimGroupMember.Type.GROUP,
                        Arrays.asList(ScimGroupMember.Role.READER));
                try {
                    scimGroupMembershipManager.addMember(group.getId(), member);
                } catch (MemberAlreadyExistsException x) {
                }
            }
        } finally {
            IdentityZoneHolder.set(originalZone);
        }
        return newGroup;
    }

}