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

Java tutorial

Introduction

Here is the source code for org.cloudfoundry.identity.uaa.scim.endpoints.ScimGroupEndpointsTests.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 org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cloudfoundry.identity.uaa.constants.OriginKeys;
import org.cloudfoundry.identity.uaa.resources.SearchResults;
import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory;
import org.cloudfoundry.identity.uaa.resources.jdbc.LimitSqlAdapterFactory;
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.ScimUser;
import org.cloudfoundry.identity.uaa.scim.bootstrap.ScimExternalGroupBootstrap;
import org.cloudfoundry.identity.uaa.scim.exception.InvalidScimResourceException;
import org.cloudfoundry.identity.uaa.scim.exception.ScimException;
import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceAlreadyExistsException;
import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceNotFoundException;
import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupExternalMembershipManager;
import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupMembershipManager;
import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupProvisioning;
import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimUserProvisioning;
import org.cloudfoundry.identity.uaa.scim.test.TestUtils;
import org.cloudfoundry.identity.uaa.scim.validate.PasswordValidator;
import org.cloudfoundry.identity.uaa.security.SecurityContextAccessor;
import org.cloudfoundry.identity.uaa.test.JdbcTestBase;
import org.cloudfoundry.identity.uaa.web.ExceptionReportHttpMessageConverter;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.HttpMediaTypeException;
import org.springframework.web.servlet.View;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class ScimGroupEndpointsTests extends JdbcTestBase {

    Log logger = LogFactory.getLog(getClass());

    private volatile JdbcScimGroupProvisioning dao;

    private volatile JdbcScimUserProvisioning udao;

    private volatile JdbcScimGroupMembershipManager mm;

    private volatile JdbcScimGroupExternalMembershipManager em;

    private volatile ScimExternalGroupBootstrap externalGroupBootstrap;

    private volatile ScimGroupEndpoints endpoints;

    private volatile ScimUserEndpoints userEndpoints;

    private volatile List<String> groupIds;

    private volatile List<String> userIds;

    private static final String SQL_INJECTION_FIELDS = "displayName,version,created,lastModified";

    @Rule
    public ExpectedException expectedEx = ExpectedException.none();

    @Before
    public void initScimGroupEndpointsTests() throws Exception {
        TestUtils.deleteFrom(dataSource, "users", "groups", "group_membership");
        JdbcTemplate template = jdbcTemplate;
        JdbcPagingListFactory pagingListFactory = new JdbcPagingListFactory(template,
                LimitSqlAdapterFactory.getLimitSqlAdapter());
        dao = new JdbcScimGroupProvisioning(template, pagingListFactory);
        udao = new JdbcScimUserProvisioning(template, pagingListFactory);
        mm = new JdbcScimGroupMembershipManager(template, pagingListFactory);
        mm.setScimGroupProvisioning(dao);
        mm.setScimUserProvisioning(udao);
        mm.setDefaultUserGroups(Collections.singleton("uaa.user"));

        em = new JdbcScimGroupExternalMembershipManager(template, pagingListFactory);
        em.setScimGroupProvisioning(dao);

        endpoints = new ScimGroupEndpoints(dao, mm);
        endpoints.setExternalMembershipManager(em);

        userEndpoints = new ScimUserEndpoints();
        userEndpoints.setScimUserProvisioning(udao);
        userEndpoints.setScimGroupMembershipManager(mm);
        userEndpoints.setPasswordValidator(mock(PasswordValidator.class));

        groupIds = new ArrayList<String>();
        userIds = new ArrayList<String>();
        groupIds.add(addGroup("uaa.resource",
                Arrays.asList(createMember(ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN),
                        createMember(ScimGroupMember.Type.GROUP, ScimGroupMember.GROUP_MEMBER),
                        createMember(ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN))));
        groupIds.add(addGroup("uaa.admin", Collections.<ScimGroupMember>emptyList()));
        groupIds.add(addGroup("uaa.none",
                Arrays.asList(createMember(ScimGroupMember.Type.USER, ScimGroupMember.GROUP_MEMBER),
                        createMember(ScimGroupMember.Type.GROUP, ScimGroupMember.GROUP_ADMIN))));

        externalGroupBootstrap = new ScimExternalGroupBootstrap(dao, em);
        externalGroupBootstrap.setAddNonExistingGroups(true);

        Map<String, Map<String, List>> externalGroups = new HashMap<>();
        Map<String, List> externalToInternalMap = new HashMap<>();
        externalToInternalMap.put("cn=test_org,ou=people,o=springsource,o=org",
                Collections.singletonList("organizations.acme"));
        externalToInternalMap.put("cn=developers,ou=scopes,dc=test,dc=com",
                Collections.singletonList("internal.read"));
        externalToInternalMap.put("cn=operators,ou=scopes,dc=test,dc=com",
                Collections.singletonList("internal.write"));
        externalToInternalMap.put("cn=superusers,ou=scopes,dc=test,dc=com",
                Arrays.asList("internal.everything", "internal.superuser"));
        externalGroups.put(OriginKeys.LDAP, externalToInternalMap);
        externalGroupBootstrap.setExternalGroupMaps(externalGroups);
        externalGroupBootstrap.afterPropertiesSet();
    }

    private String addGroup(String name, List<ScimGroupMember> m) {
        ScimGroup g = new ScimGroup(null, name, IdentityZoneHolder.get().getId());
        g = dao.create(g);
        for (ScimGroupMember member : m) {
            mm.addMember(g.getId(), member);
        }
        return g.getId();
    }

    private ScimGroupMember createMember(ScimGroupMember.Type t, List<ScimGroupMember.Role> a) {
        String id = UUID.randomUUID().toString();
        if (t == ScimGroupMember.Type.USER) {
            id = userEndpoints.createUser(TestUtils.scimUserInstance(id), new MockHttpServletRequest(),
                    new MockHttpServletResponse()).getId();
            userIds.add(id);
        } else {
            id = dao.create(new ScimGroup(null, id, IdentityZoneHolder.get().getId())).getId();
            groupIds.add(id);
        }
        return new ScimGroupMember(id, t, a);
    }

    private void deleteGroup(String name) {
        for (ScimGroup g : dao.query("displayName eq \"" + name + "\"")) {
            dao.delete(g.getId(), g.getVersion());
            mm.removeMembersByGroupId(g.getId());
        }
    }

    private void validateSearchResults(SearchResults<?> results, int expectedSize) {
        assertNotNull(results);
        assertNotNull(results.getResources());
        assertEquals(expectedSize, results.getResources().size());
    }

    private void validateGroup(ScimGroup g, String expectedName, int expectedMemberCount) {
        assertNotNull(g);
        assertNotNull(g.getId());
        assertNotNull(g.getVersion());
        assertEquals(expectedName, g.getDisplayName());
        assertNotNull(g.getMembers());
        assertEquals(expectedMemberCount, g.getMembers().size());
    }

    private void validateUserGroups(String id, String... gnm) {
        ScimUser user = userEndpoints.getUser(id, new MockHttpServletResponse());
        Set<String> expectedAuthorities = new HashSet<String>();
        expectedAuthorities.addAll(Arrays.asList(gnm));
        expectedAuthorities.add("uaa.user");
        assertNotNull(user.getGroups());
        logger.debug("user's groups: " + user.getGroups() + ", expecting: " + expectedAuthorities);
        assertEquals(expectedAuthorities.size(), user.getGroups().size());
        for (ScimUser.Group g : user.getGroups()) {
            assertTrue(expectedAuthorities.contains(g.getDisplay()));
        }
    }

    private SecurityContextAccessor mockSecurityContextAccessor(String userId) {
        SecurityContextAccessor sca = mock(SecurityContextAccessor.class);
        when(sca.getUserId()).thenReturn(userId);
        when(sca.isUser()).thenReturn(true);
        return sca;
    }

    @Test
    public void testListGroups() throws Exception {
        validateSearchResults(endpoints.listGroups("id,displayName", "id pr", "created", "ascending", 1, 100), 11);
    }

    @Test
    public void testListGroups_Without_Description() throws Exception {
        validateSearchResults(
                endpoints.listGroups("id,displayName,description", "id pr", "created", "ascending", 1, 100), 11);
        validateSearchResults(
                endpoints.listGroups("id,displayName,meta.lastModified", "id pr", "created", "ascending", 1, 100),
                11);
        validateSearchResults(
                endpoints.listGroups("id,displayName,zoneId", "id pr", "created", "ascending", 1, 100), 11);
    }

    @Test
    public void testListExternalGroups() throws Exception {
        validateSearchResults(endpoints.getExternalGroups(1, 100, ""), 5);
    }

    @Test
    public void testListExternalGroupsInvalidFilter() throws Exception {
        try {
            endpoints.getExternalGroups(1, 100, "dasda dasdas dasdas");
        } catch (ScimException x) {
            assertTrue(x.getMessage().startsWith("Invalid filter"));
        }
    }

    @Test
    public void mapExternalGroup_truncatesLeadingAndTrailingSpaces_InExternalGroupName() throws Exception {
        ScimGroupExternalMember member = getScimGroupExternalMember();
        assertEquals("external_group_id", member.getExternalGroup());
    }

    @Test
    public void unmapExternalGroup_truncatesLeadingAndTrailingSpaces_InExternalGroupName() throws Exception {
        ScimGroupExternalMember member = getScimGroupExternalMember();
        member = endpoints.unmapExternalGroup(member.getGroupId(), "  \nexternal_group_id\n", OriginKeys.LDAP);
        assertEquals("external_group_id", member.getExternalGroup());
    }

    @Test
    public void unmapExternalGroupUsingName_truncatesLeadingAndTrailingSpaces_InExternalGroupName()
            throws Exception {
        ScimGroupExternalMember member = getScimGroupExternalMember();
        member = endpoints.unmapExternalGroupUsingName(member.getDisplayName(), "  \nexternal_group_id\n");
        assertEquals("external_group_id", member.getExternalGroup());
    }

    private ScimGroupExternalMember getScimGroupExternalMember() {
        ScimGroupExternalMember member = new ScimGroupExternalMember(groupIds.get(0), "  external_group_id  ");
        member = endpoints.mapExternalGroup(member);
        return member;
    }

    @Test
    public void testFindPageOfIds() {
        SearchResults<?> results = endpoints.listGroups("id", "id pr", null, "ascending", 1, 1);
        assertEquals(11, results.getTotalResults());
        assertEquals(1, results.getResources().size());
    }

    @Test
    public void testFindMultiplePagesOfIds() {
        int pageSize = dao.getPageSize();
        dao.setPageSize(1);
        try {
            SearchResults<?> results = endpoints.listGroups("id", "id pr", null, "ascending", 1, 100);
            assertEquals(11, results.getTotalResults());
            assertEquals(11, results.getResources().size());
        } finally {
            dao.setPageSize(pageSize);
        }
    }

    @Test
    public void testListGroupsWithNameEqFilter() {
        validateSearchResults(endpoints.listGroups("id,displayName", "displayName eq \"uaa.user\"", "created",
                "ascending", 1, 100), 1);
    }

    @Test
    public void testListGroupsWithNameCoFilter() {
        validateSearchResults(
                endpoints.listGroups("id,displayName", "displayName co \"admin\"", "created", "ascending", 1, 100),
                1);
    }

    @Test
    public void testListGroupsWithInvalidFilterFails() {
        expectedEx.expect(ScimException.class);
        expectedEx.expectMessage("Invalid filter expression");
        endpoints.listGroups("id,displayName", "displayName cr \"admin\"", "created", "ascending", 1, 100);
    }

    @Test
    public void testListGroupsWithInvalidAttributes() {
        validateSearchResults(endpoints.listGroups("id,displayNameee", "displayName co \"admin\"", "created",
                "ascending", 1, 100), 1);
    }

    @Test
    public void testListGroupsWithNullAttributes() {
        validateSearchResults(
                endpoints.listGroups(null, "displayName co \"admin\"", "created", "ascending", 1, 100), 1);
    }

    @Test
    public void testSqlInjectionAttackFailsCorrectly() {
        expectedEx.expect(ScimException.class);
        expectedEx.expectMessage("Invalid filter expression");
        endpoints.listGroups("id,display", "displayName='something'; select " + SQL_INJECTION_FIELDS
                + " from groups where displayName='something'", "created", "ascending", 1, 100);
    }

    @Test
    public void legacyTestListGroupsWithNameEqFilter() {
        validateSearchResults(
                endpoints.listGroups("id,displayName", "displayName eq 'uaa.user'", "created", "ascending", 1, 100),
                1);
    }

    @Test
    public void legacyTestListGroupsWithNameCoFilter() {
        validateSearchResults(
                endpoints.listGroups("id,displayName", "displayName co 'admin'", "created", "ascending", 1, 100),
                1);
    }

    @Test
    public void legacyTestListGroupsWithInvalidFilterFails() {
        expectedEx.expect(ScimException.class);
        expectedEx.expectMessage("Invalid filter expression");
        endpoints.listGroups("id,displayName", "displayName cr 'admin'", "created", "ascending", 1, 100);
    }

    @Test
    public void legacyTestListGroupsWithInvalidAttributes() {
        validateSearchResults(
                endpoints.listGroups("id,displayNameee", "displayName co 'admin'", "created", "ascending", 1, 100),
                1);
    }

    @Test
    public void legacyTestListGroupsWithNullAttributes() {
        validateSearchResults(endpoints.listGroups(null, "displayName co 'admin'", "created", "ascending", 1, 100),
                1);
    }

    @Test
    public void testGetGroup() throws Exception {
        MockHttpServletResponse httpServletResponse = new MockHttpServletResponse();
        ScimGroup g = endpoints.getGroup(groupIds.get(groupIds.size() - 1), httpServletResponse);
        validateGroup(g, "uaa.none", 2);
        assertEquals("\"0\"", httpServletResponse.getHeader("ETag"));
    }

    @Test
    public void testGetNonExistentGroupFails() {
        expectedEx.expect(ScimResourceNotFoundException.class);
        endpoints.getGroup("wrongid", new MockHttpServletResponse());
    }

    @Test
    public void testCreateGroup() throws Exception {
        ScimGroup g = new ScimGroup(null, "clients.read", IdentityZoneHolder.get().getId());
        g.setMembers(Arrays.asList(createMember(ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN)));
        MockHttpServletResponse httpServletResponse = new MockHttpServletResponse();
        ScimGroup g1 = endpoints.createGroup(g, httpServletResponse);
        assertEquals("\"0\"", httpServletResponse.getHeader("ETag"));

        validateGroup(g1, "clients.read", 1);
        validateUserGroups(g.getMembers().get(0).getMemberId(), "clients.read");

        deleteGroup("clients.read");
    }

    @Test
    public void testCreateExistingGroupFails() {
        ScimGroup g = new ScimGroup(null, "clients.read", IdentityZoneHolder.get().getId());
        g.setMembers(Arrays.asList(createMember(ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN)));
        endpoints.createGroup(g, new MockHttpServletResponse());
        try {
            endpoints.createGroup(g, new MockHttpServletResponse());
            fail("must have thrown exception");
        } catch (ScimResourceAlreadyExistsException ex) {
            validateSearchResults(
                    endpoints.listGroups("id", "displayName eq \"clients.read\"", "id", "ASC", 1, 100), 1);
        }

        deleteGroup("clients.read");
    }

    @Test
    public void testCreateGroupWithInvalidMemberFails() {
        ScimGroup g = new ScimGroup(null, "clients.read", IdentityZoneHolder.get().getId());
        g.setMembers(Arrays.asList(
                new ScimGroupMember("non-existent id", ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN)));

        try {
            endpoints.createGroup(g, new MockHttpServletResponse());
            fail("must have thrown exception");
        } catch (InvalidScimResourceException ex) {
            // ensure that the group was not created
            validateSearchResults(
                    endpoints.listGroups("id", "displayName eq \"clients.read\"", "id", "ASC", 1, 100), 0);
        }
    }

    @Test
    public void testUpdateGroup() throws Exception {
        ScimGroup g = new ScimGroup(null, "clients.read", IdentityZoneHolder.get().getId());
        g.setMembers(Arrays.asList(createMember(ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN)));
        g = endpoints.createGroup(g, new MockHttpServletResponse());
        validateUserGroups(g.getMembers().get(0).getMemberId(), "clients.read");

        g.setDisplayName("superadmin");
        g.getMembers().get(0).setRoles(ScimGroupMember.GROUP_MEMBER);
        MockHttpServletResponse httpServletResponse = new MockHttpServletResponse();
        ScimGroup g1 = endpoints.updateGroup(g, g.getId(), "*", httpServletResponse);
        assertEquals("\"1\"", httpServletResponse.getHeader("ETag"));

        validateGroup(g1, "superadmin", 1);
        assertEquals(ScimGroupMember.GROUP_MEMBER, g1.getMembers().get(0).getRoles());
        validateUserGroups(g.getMembers().get(0).getMemberId(), "superadmin");
    }

    @Test
    public void testUpdateGroupQuotedEtag() throws Exception {
        ScimGroup g = new ScimGroup(null, "clients.read", IdentityZoneHolder.get().getId());
        g.setMembers(Arrays.asList(createMember(ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN)));
        g = endpoints.createGroup(g, new MockHttpServletResponse());
        validateUserGroups(g.getMembers().get(0).getMemberId(), "clients.read");

        g.setDisplayName("superadmin");
        g.getMembers().get(0).setRoles(ScimGroupMember.GROUP_MEMBER);
        MockHttpServletResponse httpServletResponse = new MockHttpServletResponse();
        ScimGroup g1 = endpoints.updateGroup(g, g.getId(), "\"*\"", httpServletResponse);
        assertEquals("\"1\"", httpServletResponse.getHeader("ETag"));

        validateGroup(g1, "superadmin", 1);
        assertEquals(ScimGroupMember.GROUP_MEMBER, g1.getMembers().get(0).getRoles());
        validateUserGroups(g.getMembers().get(0).getMemberId(), "superadmin");
    }

    @Test
    public void testUpdateGroupRemoveMembers() throws Exception {
        ScimGroup g = new ScimGroup(null, "clients.read", IdentityZoneHolder.get().getId());
        g.setMembers(Arrays.asList(createMember(ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN)));
        g = endpoints.createGroup(g, new MockHttpServletResponse());
        validateUserGroups(g.getMembers().get(0).getMemberId(), "clients.read");

        g.setDisplayName("superadmin");
        g.setMembers(new ArrayList<ScimGroupMember>());
        MockHttpServletResponse httpServletResponse = new MockHttpServletResponse();
        ScimGroup g1 = endpoints.updateGroup(g, g.getId(), "*", httpServletResponse);
        assertEquals("\"1\"", httpServletResponse.getHeader("ETag"));

        validateGroup(g1, "superadmin", 0);
    }

    @Test(expected = ScimException.class)
    public void testUpdateGroupNullEtag() throws Exception {
        ScimGroup g = new ScimGroup(null, "clients.read", IdentityZoneHolder.get().getId());
        g.setMembers(Arrays.asList(createMember(ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN)));
        g = endpoints.createGroup(g, new MockHttpServletResponse());
        validateUserGroups(g.getMembers().get(0).getMemberId(), "clients.read");

        g.setDisplayName("superadmin");
        g.getMembers().get(0).setRoles(ScimGroupMember.GROUP_MEMBER);
        MockHttpServletResponse httpServletResponse = new MockHttpServletResponse();
        endpoints.updateGroup(g, g.getId(), null, httpServletResponse);
    }

    @Test(expected = ScimException.class)
    public void testUpdateGroupNoEtag() throws Exception {
        ScimGroup g = new ScimGroup(null, "clients.read", IdentityZoneHolder.get().getId());
        g.setMembers(Arrays.asList(createMember(ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN)));
        g = endpoints.createGroup(g, new MockHttpServletResponse());
        validateUserGroups(g.getMembers().get(0).getMemberId(), "clients.read");

        g.setDisplayName("superadmin");
        g.getMembers().get(0).setRoles(ScimGroupMember.GROUP_MEMBER);
        MockHttpServletResponse httpServletResponse = new MockHttpServletResponse();
        endpoints.updateGroup(g, g.getId(), "", httpServletResponse);
    }

    @Test(expected = ScimException.class)
    public void testUpdateGroupInvalidEtag() throws Exception {
        ScimGroup g = new ScimGroup(null, "clients.read", IdentityZoneHolder.get().getId());
        g.setMembers(Arrays.asList(createMember(ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN)));
        g = endpoints.createGroup(g, new MockHttpServletResponse());
        validateUserGroups(g.getMembers().get(0).getMemberId(), "clients.read");

        g.setDisplayName("superadmin");
        g.getMembers().get(0).setRoles(ScimGroupMember.GROUP_MEMBER);
        MockHttpServletResponse httpServletResponse = new MockHttpServletResponse();
        endpoints.updateGroup(g, g.getId(), "abc", httpServletResponse);
    }

    @Test
    public void testUpdateNonUniqueDisplayNameFails() {
        ScimGroup g1 = new ScimGroup(null, "clients.read", IdentityZoneHolder.get().getId());
        g1.setMembers(Arrays.asList(createMember(ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN)));
        g1 = endpoints.createGroup(g1, new MockHttpServletResponse());

        ScimGroup g2 = new ScimGroup(null, "clients.write", IdentityZoneHolder.get().getId());
        g2.setMembers(Arrays.asList(createMember(ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN)));
        g2 = endpoints.createGroup(g2, new MockHttpServletResponse());

        g1.setDisplayName("clients.write");
        try {
            endpoints.updateGroup(g1, g1.getId(), "*", new MockHttpServletResponse());
            fail("must have thrown exception");
        } catch (InvalidScimResourceException ex) {
            validateSearchResults(
                    endpoints.listGroups("id", "displayName eq \"clients.write\"", "id", "ASC", 1, 100), 1);
            validateSearchResults(
                    endpoints.listGroups("id", "displayName eq \"clients.read\"", "id", "ASC", 1, 100), 1);
        }

        deleteGroup("clients.read");
        deleteGroup("clients.write");
    }

    @Test
    public void testUpdateWithInvalidMemberFails() {
        ScimGroup g1 = new ScimGroup(null, "clients.read", IdentityZoneHolder.get().getId());
        g1.setMembers(Arrays.asList(createMember(ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN)));
        g1 = endpoints.createGroup(g1, new MockHttpServletResponse());

        g1.setMembers(Arrays.asList(
                new ScimGroupMember("non-existent id", ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN)));
        g1.setDisplayName("clients.write");

        try {
            endpoints.updateGroup(g1, g1.getId(), "*", new MockHttpServletResponse());
            fail("must have thrown exception");
        } catch (ScimException ex) {
            // ensure that displayName was not updated
            g1 = endpoints.getGroup(g1.getId(), new MockHttpServletResponse());
            validateGroup(g1, "clients.read", 0);
            validateSearchResults(
                    endpoints.listGroups("id", "displayName eq \"clients.write\"", "id", "ASC", 1, 100), 0);
        }

        deleteGroup("clients.read");
    }

    @Test
    public void testUpdateInvalidVersionFails() {
        ScimGroup g1 = new ScimGroup(null, "clients.read", IdentityZoneHolder.get().getId());
        g1.setMembers(Arrays.asList(createMember(ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN)));
        g1 = endpoints.createGroup(g1, new MockHttpServletResponse());

        g1.setDisplayName("clients.write");

        try {
            endpoints.updateGroup(g1, g1.getId(), "version", new MockHttpServletResponse());
        } catch (ScimException ex) {
            assertTrue("Wrong exception message", ex.getMessage().contains("Invalid version"));
            validateSearchResults(
                    endpoints.listGroups("id", "displayName eq \"clients.write\"", "id", "ASC", 1, 100), 0);
            validateSearchResults(
                    endpoints.listGroups("id", "displayName eq \"clients.read\"", "id", "ASC", 1, 100), 1);
        }

        deleteGroup("clients.read");
    }

    @Test
    public void testUpdateGroupWithNullEtagFails() {
        ScimGroup g1 = new ScimGroup(null, "clients.read", IdentityZoneHolder.get().getId());
        g1.setMembers(Arrays.asList(createMember(ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN)));
        g1 = endpoints.createGroup(g1, new MockHttpServletResponse());

        g1.setDisplayName("clients.write");

        try {
            endpoints.updateGroup(g1, g1.getId(), null, new MockHttpServletResponse());
        } catch (ScimException ex) {
            assertTrue("Wrong exception message", ex.getMessage().contains("Missing If-Match"));
            validateSearchResults(
                    endpoints.listGroups("id", "displayName eq \"clients.write\"", "id", "ASC", 1, 100), 0);
            validateSearchResults(
                    endpoints.listGroups("id", "displayName eq \"clients.read\"", "id", "ASC", 1, 100), 1);
        }

        deleteGroup("clients.read");
    }

    @Test
    public void testUpdateWithQuotedVersionSucceeds() {
        ScimGroup g1 = new ScimGroup(null, "clients.read", IdentityZoneHolder.get().getId());
        g1.setMembers(Arrays.asList(createMember(ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN)));
        g1 = endpoints.createGroup(g1, new MockHttpServletResponse());

        g1.setDisplayName("clients.write");

        endpoints.updateGroup(g1, g1.getId(), "\"*", new MockHttpServletResponse());
        endpoints.updateGroup(g1, g1.getId(), "*\"", new MockHttpServletResponse());
        validateSearchResults(endpoints.listGroups("id", "displayName eq \"clients.write\"", "id", "ASC", 1, 100),
                1);
        validateSearchResults(endpoints.listGroups("id", "displayName eq \"clients.read\"", "id", "ASC", 1, 100),
                0);

        deleteGroup("clients.write");
    }

    @Test
    public void testUpdateWrongVersionFails() {
        ScimGroup g1 = new ScimGroup(null, "clients.read", IdentityZoneHolder.get().getId());
        g1.setMembers(Arrays.asList(createMember(ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN)));
        g1 = endpoints.createGroup(g1, new MockHttpServletResponse());

        g1.setDisplayName("clients.write");

        try {
            endpoints.updateGroup(g1, g1.getId(), String.valueOf(g1.getVersion() + 23),
                    new MockHttpServletResponse());
        } catch (ScimException ex) {
            validateSearchResults(
                    endpoints.listGroups("id", "displayName eq \"clients.write\"", "id", "ASC", 1, 100), 0);
            validateSearchResults(
                    endpoints.listGroups("id", "displayName eq \"clients.read\"", "id", "ASC", 1, 100), 1);
        }

        deleteGroup("clients.read");
    }

    @Test
    public void testUpdateGroupWithNoMembers() {
        ScimGroup g = new ScimGroup(null, "clients.read", IdentityZoneHolder.get().getId());
        g.setMembers(Arrays.asList(createMember(ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN)));
        g = endpoints.createGroup(g, new MockHttpServletResponse());
        validateUserGroups(g.getMembers().get(0).getMemberId(), "clients.read");

        g.setDisplayName("someadmin");
        g.setMembers(null);
        ScimGroup g1 = endpoints.updateGroup(g, g.getId(), "*", new MockHttpServletResponse());
        validateGroup(g1, "someadmin", 0);

        deleteGroup("clients.read");
    }

    @Test
    public void testDeleteGroup() throws Exception {
        ScimGroup g = new ScimGroup(null, "clients.read", IdentityZoneHolder.get().getId());
        g.setMembers(Arrays.asList(createMember(ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN)));
        g = endpoints.createGroup(g, new MockHttpServletResponse());
        validateUserGroups(g.getMembers().get(0).getMemberId(), "clients.read");

        g = endpoints.deleteGroup(g.getId(), "*", new MockHttpServletResponse());
        try {
            endpoints.getGroup(g.getId(), new MockHttpServletResponse());
            fail("group should not exist");
        } catch (ScimResourceNotFoundException ex) {
        }
        validateUserGroups(g.getMembers().get(0).getMemberId(), "uaa.user");
    }

    @Test
    public void testDeleteWrongVersionFails() {
        ScimGroup g = new ScimGroup(null, "clients.read", IdentityZoneHolder.get().getId());
        g.setMembers(Arrays.asList(createMember(ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN)));
        g = endpoints.createGroup(g, new MockHttpServletResponse());

        try {
            endpoints.deleteGroup(g.getId(), String.valueOf(g.getVersion() + 3), new MockHttpServletResponse());
        } catch (ScimException ex) {
            validateSearchResults(
                    endpoints.listGroups("id", "displayName eq \"clients.read\"", "id", "ASC", 1, 100), 1);
        }

        deleteGroup("clients.read");
    }

    @Test
    public void testDeleteNonExistentGroupFails() {
        expectedEx.expect(ScimResourceNotFoundException.class);
        endpoints.deleteGroup("some id", "*", new MockHttpServletResponse());
    }

    @Test
    public void testExceptionHandler() {
        Map<Class<? extends Exception>, HttpStatus> map = new HashMap<Class<? extends Exception>, HttpStatus>();
        map.put(IllegalArgumentException.class, HttpStatus.BAD_REQUEST);
        map.put(UnsupportedOperationException.class, HttpStatus.BAD_REQUEST);
        map.put(BadSqlGrammarException.class, HttpStatus.BAD_REQUEST);
        map.put(DataIntegrityViolationException.class, HttpStatus.BAD_REQUEST);
        map.put(HttpMessageConversionException.class, HttpStatus.BAD_REQUEST);
        map.put(HttpMediaTypeException.class, HttpStatus.BAD_REQUEST);
        endpoints.setStatuses(map);
        endpoints.setMessageConverters(new HttpMessageConverter<?>[] { new ExceptionReportHttpMessageConverter() });

        MockHttpServletRequest request = new MockHttpServletRequest();
        validateView(endpoints.handleException(new ScimResourceNotFoundException(""), request),
                HttpStatus.NOT_FOUND);
        validateView(endpoints.handleException(new UnsupportedOperationException(""), request),
                HttpStatus.BAD_REQUEST);
        validateView(endpoints.handleException(new BadSqlGrammarException("", "", null), request),
                HttpStatus.BAD_REQUEST);
        validateView(endpoints.handleException(new IllegalArgumentException(""), request), HttpStatus.BAD_REQUEST);
        validateView(endpoints.handleException(new DataIntegrityViolationException(""), request),
                HttpStatus.BAD_REQUEST);
    }

    private void validateView(View view, HttpStatus status) {
        MockHttpServletResponse response = new MockHttpServletResponse();
        try {
            view.render(new HashMap<String, Object>(), new MockHttpServletRequest(), response);
            assertNotNull(response.getContentAsString());
        } catch (Exception e) {
            fail("view should render correct status and body");
        }
        assertEquals(status.value(), response.getStatus());
    }

    @Test
    public void testPatch() {
        ScimGroup g1 = new ScimGroup(null, "name", IdentityZoneHolder.get().getId());
        g1.setDescription("description");

        g1 = dao.create(g1);

        ScimGroup patch = new ScimGroup("NewName");
        patch.setId(g1.getId());

        patch = endpoints.patchGroup(patch, patch.getId(), Integer.toString(g1.getVersion()),
                new MockHttpServletResponse());

        assertEquals("NewName", patch.getDisplayName());
        assertEquals(g1.getDescription(), patch.getDescription());
    }

    @Test(expected = ScimException.class)
    public void testPatchInvalidResourceFails() {
        ScimGroup g1 = new ScimGroup(null, "name", IdentityZoneHolder.get().getId());
        g1.setDescription("description");

        ScimGroup patch = endpoints.patchGroup(g1, "id", "0", new MockHttpServletResponse());

    }

    @Test
    public void testPatchAddMembers() {
        ScimGroup g1 = new ScimGroup(null, "name", IdentityZoneHolder.get().getId());
        g1.setDescription("description");

        g1 = dao.create(g1);

        ScimGroup patch = new ScimGroup();
        assertEquals(null, g1.getMembers());
        assertEquals(null, patch.getMembers());
        patch.setMembers(Arrays.asList(createMember(ScimGroupMember.Type.USER, ScimGroupMember.GROUP_ADMIN)));
        assertEquals(1, patch.getMembers().size());

        patch = endpoints.patchGroup(patch, g1.getId(), "0", new MockHttpServletResponse());

        assertEquals(1, patch.getMembers().size());
        ScimGroupMember member = patch.getMembers().get(0);
        assertEquals(ScimGroupMember.Type.USER, member.getType());
        assertEquals(ScimGroupMember.GROUP_ADMIN, member.getRoles());
    }

    @Test(expected = ScimException.class)
    public void testPatchIncorrectEtagFails() {
        ScimGroup g1 = new ScimGroup(null, "name", IdentityZoneHolder.get().getId());
        g1.setDescription("description");

        g1 = dao.create(g1);

        ScimGroup patch = new ScimGroup("NewName");
        patch.setId(g1.getId());

        patch = endpoints.patchGroup(patch, patch.getId(), Integer.toString(g1.getVersion() + 1),
                new MockHttpServletResponse());
    }
}