All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.cloudfoundry.identity.uaa.scim.endpoints.ScimGroupEndpoints Maven / Gradle / Ivy

/*******************************************************************************
 *     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.jayway.jsonpath.JsonPathException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cloudfoundry.identity.uaa.resources.AttributeNameMapper;
import org.cloudfoundry.identity.uaa.resources.SearchResults;
import org.cloudfoundry.identity.uaa.resources.SearchResultsFactory;
import org.cloudfoundry.identity.uaa.resources.SimpleAttributeNameMapper;
import org.cloudfoundry.identity.uaa.resources.jdbc.SimpleSearchQueryConverter;
import org.cloudfoundry.identity.uaa.scim.ScimCore;
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.exception.InvalidScimResourceException;
import org.cloudfoundry.identity.uaa.scim.exception.MemberAlreadyExistsException;
import org.cloudfoundry.identity.uaa.scim.exception.ScimException;
import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceNotFoundException;
import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupExternalMembershipManager;
import org.cloudfoundry.identity.uaa.security.DefaultSecurityContextAccessor;
import org.cloudfoundry.identity.uaa.security.SecurityContextAccessor;
import org.cloudfoundry.identity.uaa.util.UaaPagingUtils;
import org.cloudfoundry.identity.uaa.web.ConvertingExceptionView;
import org.cloudfoundry.identity.uaa.web.ExceptionReport;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.stereotype.Controller;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.View;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import static java.util.Optional.ofNullable;
import static org.cloudfoundry.identity.uaa.constants.OriginKeys.LDAP;
import static org.cloudfoundry.identity.uaa.zone.ZoneManagementScopes.ZONE_MANAGING_SCOPE_REGEX;
import static org.springframework.util.StringUtils.hasText;

@Controller
public class ScimGroupEndpoints {

    public static final String E_TAG = "ETag";

    private final ScimGroupProvisioning dao;

    private ScimGroupMembershipManager membershipManager;

    private JdbcScimGroupExternalMembershipManager externalMembershipManager;

    private Map, HttpStatus> statuses = new HashMap<>();

    private HttpMessageConverter[] messageConverters = new RestTemplate().getMessageConverters().toArray(
        new HttpMessageConverter[0]);

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

    private SecurityContextAccessor securityContextAccessor = new DefaultSecurityContextAccessor();

    private int groupMaxCount;

    public void setSecurityContextAccessor(SecurityContextAccessor securityContextAccessor) {
        this.securityContextAccessor = securityContextAccessor;
    }

    public void setStatuses(Map, HttpStatus> statuses) {
        this.statuses = statuses;
    }

    public void setMessageConverters(HttpMessageConverter[] messageConverters) {
        this.messageConverters = messageConverters;
    }

    public JdbcScimGroupExternalMembershipManager getExternalMembershipManager() {
        return externalMembershipManager;
    }

    public void setExternalMembershipManager(JdbcScimGroupExternalMembershipManager externalMembershipManager) {
        this.externalMembershipManager = externalMembershipManager;
    }

    public ScimGroupEndpoints(ScimGroupProvisioning scimGroupProvisioning, ScimGroupMembershipManager membershipManager) {
        this.dao = scimGroupProvisioning;
        this.membershipManager = membershipManager;
    }

    private boolean isMember(ScimGroup group, String userId) {
        if (null == userId) {
            return true;
        }
        for (ScimGroupMember member : group.getMembers()) {
            if (member.getMemberId().equals(userId)) {
                return true;
            }
        }
        return false;
    }

    private List filterForCurrentUser(List input, int startIndex, int count, boolean includeMembers) {
        List response = new ArrayList();
        int expectedResponseSize = Math.min(count, input.size());
        boolean needMore = response.size() < expectedResponseSize;
        while (needMore && startIndex <= input.size()) {
            for (ScimGroup group : UaaPagingUtils.subList(input, startIndex, count)) {
                if (includeMembers) {
                    group.setMembers(membershipManager.getMembers(group.getId(), false, IdentityZoneHolder.get().getId()));
                }
                response.add(group);
                needMore = response.size() < expectedResponseSize;
                if (!needMore) {
                    break;
                }
            }
            startIndex += count;
        }
        return response;
    }

    @RequestMapping(value = {"/Groups"}, method = RequestMethod.GET)
    @ResponseBody
    public SearchResults listGroups(
        @RequestParam(value = "attributes", required = false) String attributesCommaSeparated,
        @RequestParam(required = false, defaultValue = "id pr") String filter,
        @RequestParam(required = false, defaultValue = "created") String sortBy,
        @RequestParam(required = false, defaultValue = "ascending") String sortOrder,
        @RequestParam(required = false, defaultValue = "1") int startIndex,
        @RequestParam(required = false, defaultValue = "100") int count) {

        if (count > groupMaxCount) {
            count = groupMaxCount;
        }

        List result;
        try {
            result = dao.query(filter, sortBy, "ascending".equalsIgnoreCase(sortOrder), IdentityZoneHolder.get().getId());
        } catch (IllegalArgumentException e) {
            throw new ScimException("Invalid filter expression: [" + filter + "]", HttpStatus.BAD_REQUEST);
        }

        List input;
        if (!StringUtils.hasLength(attributesCommaSeparated)) {
            input = filterForCurrentUser(result, startIndex, count, true);
            return new SearchResults<>(Arrays.asList(ScimCore.SCHEMAS), input, startIndex, count,
                result.size());
        }

        AttributeNameMapper mapper = new SimpleAttributeNameMapper(Collections.emptyMap());

        String[] attributes = attributesCommaSeparated.split(",");
        input = filterForCurrentUser(result, startIndex, count, Arrays.asList(attributes).contains("members"));

        try {
            return SearchResultsFactory.buildSearchResultFrom(input, startIndex, count, result.size(), attributes,
                mapper, Arrays.asList(ScimCore.SCHEMAS));
        } catch (JsonPathException e) {
            throw new ScimException("Invalid attributes: [" + attributesCommaSeparated + "]", HttpStatus.BAD_REQUEST);
        }
    }

    @RequestMapping(value = {"/Groups/External/list"}, method = RequestMethod.GET)
    @ResponseBody
    @Deprecated
    public SearchResults listExternalGroups(
        @RequestParam(required = false, defaultValue = "1") int startIndex,
        @RequestParam(required = false, defaultValue = "100") int count,
        @RequestParam(required = false, defaultValue = "") String filter) {
        return getExternalGroups(startIndex, count, filter, "", "");
    }

    @RequestMapping(value = {"/Groups/External"}, method = RequestMethod.GET)
    @ResponseBody
    public SearchResults getExternalGroups(
        @RequestParam(required = false, defaultValue = "1") int startIndex,
        @RequestParam(required = false, defaultValue = "100") int count,
        @RequestParam(required = false, defaultValue = "") String filter,
        @RequestParam(required = false, defaultValue = "") String origin,
        @RequestParam(required = false, defaultValue = "") String externalGroup) {

        if (hasText(filter)) {
            if (hasText(origin) || hasText(externalGroup)) {
                throw new ScimException("Deprecated filter parameter may not be used in conjunction with origin or externalGroup parameters", HttpStatus.BAD_REQUEST);
            }
            SimpleSearchQueryConverter converter = new SimpleSearchQueryConverter();
            try {
                MultiValueMap filterData = converter.getFilterValues(filter, Arrays.asList("origin", "externalgroup"));
                origin = (ofNullable(filterData.getFirst("origin")).orElse(origin)).toString();
                externalGroup = (ofNullable(filterData.getFirst("externalGroup")).orElse(externalGroup)).toString();
            } catch (IllegalArgumentException e) {
                throw new ScimException("Filter not supported, please use origin and externalGroup parameters", e, HttpStatus.BAD_REQUEST);
            }
        }

        List result;
        try {
            result = new ArrayList(externalMembershipManager.getExternalGroupMappings(IdentityZoneHolder.get().getId()));
        } catch (IllegalArgumentException e) {
            throw new ScimException("Invalid filter expression: [" + filter + "]", e, HttpStatus.BAD_REQUEST);
        }
        final String filterOrigin = origin, filterGroup = externalGroup;
        result.removeIf(em -> hasText(filterOrigin) && !em.getOrigin().equals(filterOrigin));
        result.removeIf(em -> hasText(filterGroup) && !em.getExternalGroup().equals(filterGroup));

        return SearchResultsFactory.cropAndBuildSearchResultFrom(
            result,
            startIndex,
            count,
            result.size(),
            new String[]{"groupId", "displayName", "externalGroup", "origin"},
            Arrays.asList(ScimCore.SCHEMAS));
    }

    @RequestMapping(value = {"/Groups/External"}, method = RequestMethod.POST)
    @ResponseBody
    @ResponseStatus(HttpStatus.CREATED)
    public ScimGroupExternalMember mapExternalGroup(@RequestBody ScimGroupExternalMember sgm) {
        try {
            String displayName = sgm.getDisplayName();
            String groupId = hasText(sgm.getGroupId()) ? sgm.getGroupId() : getGroupId(displayName);
            String externalGroup = hasText(sgm.getExternalGroup()) ? sgm.getExternalGroup().trim() : sgm.getExternalGroup();
            String origin = hasText(sgm.getOrigin()) ? sgm.getOrigin() : LDAP;
            return externalMembershipManager.mapExternalGroup(groupId, externalGroup, origin, IdentityZoneHolder.get().getId());
        } catch (IllegalArgumentException e) {
            throw new ScimException(e.getMessage(), HttpStatus.BAD_REQUEST);
        } catch (ScimResourceNotFoundException e) {
            throw new ScimException(e.getMessage(), HttpStatus.NOT_FOUND);
        } catch (MemberAlreadyExistsException e) {
            throw new ScimException(e.getMessage(), HttpStatus.CONFLICT);
        }
    }

    @RequestMapping(value = {"/Groups/External/groupId/{groupId}/externalGroup/{externalGroup}"}, method = RequestMethod.DELETE)
    @ResponseBody
    @ResponseStatus(HttpStatus.OK)
    @Deprecated
    public ScimGroupExternalMember deprecated2UnmapExternalGroup(@PathVariable String groupId, @PathVariable String externalGroup) {
        return unmapExternalGroup(groupId, externalGroup, null);
    }

    @RequestMapping(value = {"/Groups/External/groupId/{groupId}/externalGroup/{externalGroup}/origin/{origin}"}, method = RequestMethod.DELETE)
    @ResponseBody
    @ResponseStatus(HttpStatus.OK)
    public ScimGroupExternalMember unmapExternalGroup(@PathVariable String groupId,
                                                      @PathVariable String externalGroup,
                                                      @PathVariable String origin) {
        try {
            if (!hasText(origin)) {
                origin = LDAP;
            }
            return externalMembershipManager.unmapExternalGroup(groupId, externalGroup.trim(), origin, IdentityZoneHolder.get().getId());
        } catch (IllegalArgumentException e) {
            throw new ScimException(e.getMessage(), HttpStatus.BAD_REQUEST);
        } catch (ScimResourceNotFoundException e) {
            throw new ScimException(e.getMessage(), HttpStatus.NOT_FOUND);
        } catch (MemberAlreadyExistsException e) {
            throw new ScimException(e.getMessage(), HttpStatus.CONFLICT);
        }
    }

    @RequestMapping(value = {"/Groups/External/id/{groupId}/{externalGroup}"}, method = RequestMethod.DELETE)
    @ResponseBody
    @ResponseStatus(HttpStatus.OK)
    @Deprecated
    public ScimGroupExternalMember deprecatedUnmapExternalGroup(@PathVariable String groupId, @PathVariable String externalGroup) {
        return unmapExternalGroup(groupId, externalGroup, LDAP);
    }

    @RequestMapping(value = {"/Groups/External/displayName/{displayName}/externalGroup/{externalGroup}"}, method = RequestMethod.DELETE)
    @ResponseBody
    @ResponseStatus(HttpStatus.OK)
    @Deprecated
    public ScimGroupExternalMember unmapExternalGroupUsingName(@PathVariable String displayName, @PathVariable String externalGroup) {
        return unmapExternalGroupUsingName(displayName, externalGroup, LDAP);
    }

    @RequestMapping(value = {"/Groups/External/displayName/{displayName}/externalGroup/{externalGroup}/origin/{origin}"}, method = RequestMethod.DELETE)
    @ResponseBody
    @ResponseStatus(HttpStatus.OK)
    public ScimGroupExternalMember unmapExternalGroupUsingName(@PathVariable String displayName,
                                                               @PathVariable String externalGroup,
                                                               @PathVariable String origin) {
        try {
            if (!hasText(origin)) {
                origin = LDAP;
            }

            return externalMembershipManager.unmapExternalGroup(getGroupId(displayName), externalGroup.trim(), origin, IdentityZoneHolder.get().getId());
        } catch (IllegalArgumentException e) {
            throw new ScimException(e.getMessage(), HttpStatus.BAD_REQUEST);
        } catch (ScimResourceNotFoundException e) {
            throw new ScimException(e.getMessage(), HttpStatus.NOT_FOUND);
        } catch (MemberAlreadyExistsException e) {
            throw new ScimException(e.getMessage(), HttpStatus.CONFLICT);
        }
    }

    @RequestMapping(value = {"/Groups/External/{displayName}/{externalGroup}"}, method = RequestMethod.DELETE)
    @ResponseBody
    @ResponseStatus(HttpStatus.OK)
    @Deprecated
    public ScimGroupExternalMember deprecatedUnmapExternalGroupUsingName(@PathVariable String displayName, @PathVariable String externalGroup) {
        return unmapExternalGroupUsingName(displayName, externalGroup);
    }

    private String getGroupId(String displayName) {
        if (displayName == null || displayName.trim().length() == 0) {
            throw new ScimException("Group not found, not name provided", HttpStatus.NOT_FOUND);
        }
        List result = dao.query("displayName eq \"" + displayName + "\"", IdentityZoneHolder.get().getId());
        if (result == null || result.size() == 0) {
            throw new ScimException("Group not found:" + displayName, HttpStatus.NOT_FOUND);
        }
        return result.get(0).getId();
    }


    @RequestMapping(value = {"/Groups/{groupId}"}, method = RequestMethod.GET)
    @ResponseBody
    public ScimGroup getGroup(@PathVariable String groupId, HttpServletResponse httpServletResponse) {
        logger.debug("retrieving group with id: " + groupId);
        ScimGroup group = dao.retrieve(groupId, IdentityZoneHolder.get().getId());
        group.setMembers(membershipManager.getMembers(groupId, false, IdentityZoneHolder.get().getId()));
        addETagHeader(httpServletResponse, group);
        return group;
    }

    @RequestMapping(value = {"/Groups"}, method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.CREATED)
    @ResponseBody
    public ScimGroup createGroup(@RequestBody ScimGroup group, HttpServletResponse httpServletResponse) {
        group.setZoneId(IdentityZoneHolder.get().getId());
        ScimGroup created = dao.create(group, IdentityZoneHolder.get().getId());
        if (group.getMembers() != null) {
            for (ScimGroupMember member : group.getMembers()) {
                try {
                    membershipManager.addMember(created.getId(), member, IdentityZoneHolder.get().getId());
                } catch (ScimException ex) {
                    logger.warn("Attempt to add invalid member: " + member.getMemberId() + " to group: " + created.getId(), ex);
                    dao.delete(created.getId(), created.getVersion(), IdentityZoneHolder.get().getId());
                    throw new InvalidScimResourceException("Invalid group member: " + member.getMemberId());
                }
            }
        }
        created.setMembers(membershipManager.getMembers(created.getId(), false, IdentityZoneHolder.get().getId()));
        addETagHeader(httpServletResponse, created);
        return created;
    }

    @RequestMapping(value = {"/Groups/{groupId}"}, method = RequestMethod.PUT)
    @ResponseBody
    public ScimGroup updateGroup(@RequestBody ScimGroup group, @PathVariable String groupId,
                                 @RequestHeader(value = "If-Match", required = false) String etag,
                                 HttpServletResponse httpServletResponse) {
        if (etag == null) {
            throw new ScimException("Missing If-Match for PUT", HttpStatus.BAD_REQUEST);
        }
        logger.debug("updating group: " + groupId);
        int version = getVersion(groupId, etag);
        group.setVersion(version);
        ScimGroup existing = getGroup(groupId, httpServletResponse);
        try {
            group.setZoneId(IdentityZoneHolder.get().getId());
            ScimGroup updated = dao.update(groupId, group, IdentityZoneHolder.get().getId());
            if (group.getMembers() != null && group.getMembers().size() > 0) {
                membershipManager.updateOrAddMembers(updated.getId(), group.getMembers(), IdentityZoneHolder.get().getId());
            } else {
                membershipManager.removeMembersByGroupId(updated.getId(), IdentityZoneHolder.get().getId());
            }
            updated.setMembers(membershipManager.getMembers(updated.getId(), false, IdentityZoneHolder.get().getId()));
            addETagHeader(httpServletResponse, updated);
            return updated;
        } catch (IncorrectResultSizeDataAccessException ex) {
            logger.error("Error updating group, restoring to previous state");
            // restore to correct state before reporting error
            existing.setVersion(getVersion(groupId, "*"));
            dao.update(groupId, existing, IdentityZoneHolder.get().getId());
            throw new ScimException(ex.getMessage(), ex, HttpStatus.CONFLICT);
        } catch (ScimResourceNotFoundException ex) {
            logger.error("Error updating group, restoring to previous state: " + existing);
            // restore to correct state before reporting error
            existing.setVersion(getVersion(groupId, "*"));
            dao.update(groupId, existing, IdentityZoneHolder.get().getId());
            throw new ScimException(ex.getMessage(), ex, HttpStatus.BAD_REQUEST);
        }
    }

    @RequestMapping(value = {"/Groups/{groupId}"}, method = RequestMethod.PATCH)
    @ResponseBody
    public ScimGroup patchGroup(@RequestBody ScimGroup patch, @PathVariable
        String groupId,
                                @RequestHeader(value = "If-Match", required = false) String etag,
                                HttpServletResponse httpServletResponse) {
        if (etag == null) {
            throw new ScimException("Missing If-Match for PATCH", HttpStatus.BAD_REQUEST);
        }
        logger.debug("patching group: " + groupId);
        int version = getVersion(groupId, etag);
        patch.setVersion(version);
        ScimGroup current = getGroup(groupId, httpServletResponse);
        current.patch(patch);
        return updateGroup(current, groupId, etag, httpServletResponse);
    }

    @RequestMapping(value = {"/Groups/{groupId}"}, method = RequestMethod.DELETE)
    @ResponseBody
    public ScimGroup deleteGroup(@PathVariable String groupId,
                                 @RequestHeader(value = "If-Match", required = false, defaultValue = "*") String etag,
                                 HttpServletResponse httpServletResponse) {
        ScimGroup group = getGroup(groupId, httpServletResponse);
        logger.debug("deleting group: " + group);
        try {
            membershipManager.removeMembersByGroupId(groupId, IdentityZoneHolder.get().getId());
            membershipManager.removeMembersByMemberId(groupId, IdentityZoneHolder.get().getId());
            dao.delete(groupId, getVersion(groupId, etag), IdentityZoneHolder.get().getId());
        } catch (IncorrectResultSizeDataAccessException ex) {
            logger.debug("error deleting group", ex);
            throw new ScimException("error deleting group: " + groupId, ex, HttpStatus.CONFLICT);
        }
        return group;
    }

    @RequestMapping(value = {"/Groups/zones"}, method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.CREATED)
    @ResponseBody
    @Deprecated
    public ScimGroup addZoneManagers(@RequestBody ScimGroup group, HttpServletResponse httpServletResponse) {
        if (!group.getDisplayName().matches(ZONE_MANAGING_SCOPE_REGEX)) {
            throw new ScimException("Invalid group name.", HttpStatus.BAD_REQUEST);
        }
        if (group.getMembers() == null || group.getMembers().size() == 0) {
            throw new ScimException("Invalid group members, you have to add at least one member.", HttpStatus.BAD_REQUEST);
        }
        try {
            ScimGroup existing = getGroup(getGroupId(group.getDisplayName()), httpServletResponse);
            List newMembers = new LinkedList<>(existing.getMembers());
            //we have an existing group - add new memberships
            for (ScimGroupMember member : group.getMembers()) {
                if (!isMember(existing, member.getMemberId())) {
                    newMembers.add(member);
                }
            }
            existing.setMembers(newMembers);
            return updateGroup(existing, existing.getId(), String.valueOf(existing.getVersion()), httpServletResponse);
        } catch (ScimException ex) {
            if (ex.getStatus().equals(HttpStatus.NOT_FOUND)) {
                return createGroup(group, httpServletResponse);
            } else {
                throw ex;
            }
        }
    }

    @RequestMapping(value = {"/Groups/zones/{userId}/{zoneId}"}, method = RequestMethod.DELETE)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    @Deprecated
    public ScimGroup deleteZoneAdmin(@PathVariable String userId, @PathVariable String zoneId, HttpServletResponse httpServletResponse) {
        return deleteZoneScope(userId, zoneId, "admin", httpServletResponse);
    }

    @RequestMapping(value = {"/Groups/zones/{userId}/{zoneId}/{scope}"}, method = RequestMethod.DELETE)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    @Deprecated
    public ScimGroup deleteZoneScope(@PathVariable String userId,
                                     @PathVariable String zoneId,
                                     @PathVariable String scope,
                                     HttpServletResponse httpServletResponse) {

        String groupName = "zones." + zoneId + "." + scope;
        if (!groupName.matches(ZONE_MANAGING_SCOPE_REGEX)) {
            throw new ScimException("Invalid group name.", HttpStatus.BAD_REQUEST);
        }
        String groupId = getGroupId(groupName);
        ScimGroup group = getGroup(groupId, httpServletResponse);
        if (!hasText(userId) || !hasText(zoneId)) {
            throw new ScimException("User ID and Zone ID are required.", HttpStatus.BAD_REQUEST);
        }
        if (!isMember(group, userId)) {
            throw new ScimException("User is not a zone admin.", HttpStatus.NOT_FOUND);
        }
        List newZoneAdmins = new LinkedList<>();
        for (ScimGroupMember member : group.getMembers()) {
            if (!member.getMemberId().equals(userId)) {
                newZoneAdmins.add(member);
            }
        }
        group.setMembers(newZoneAdmins);
        return updateGroup(group, group.getId(), String.valueOf(group.getVersion()), httpServletResponse);
    }

    @RequestMapping("/Groups/{groupId}/members/{memberId}")
    public ResponseEntity getGroupMembership(@PathVariable String groupId, @PathVariable String memberId) {
        ScimGroupMember membership = membershipManager.getMemberById(groupId, memberId, IdentityZoneHolder.get().getId());
        return new ResponseEntity<>(membership, HttpStatus.OK);
    }

    @RequestMapping(value = "/Groups/{groupId}/members", method = RequestMethod.GET)
    public ResponseEntity> listGroupMemberships(@PathVariable String groupId,
                                                                      @RequestParam(required = false, defaultValue = "false") boolean returnEntities,
                                                                      @RequestParam(required = false, defaultValue = "", name = "filter") String deprecatedFilter) {
        dao.retrieve(groupId, IdentityZoneHolder.get().getId());
        List members = membershipManager.getMembers(groupId, returnEntities, IdentityZoneHolder.get().getId());
        return new ResponseEntity<>(members, HttpStatus.OK);
    }

    @RequestMapping(value = "/Groups/{groupId}/members", method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.CREATED)
    @ResponseBody
    public ScimGroupMember addMemberToGroup(@PathVariable String groupId, @RequestBody ScimGroupMember member) {

        return membershipManager.addMember(groupId, member, IdentityZoneHolder.get().getId());
    }

    @RequestMapping(value = "/Groups/{groupId}/members/{memberId}", method = RequestMethod.DELETE)
    @ResponseBody
    @ResponseStatus(HttpStatus.OK)
    public ScimGroupMember deleteGroupMembership(@PathVariable String groupId, @PathVariable String memberId) {
        ScimGroupMember membership = membershipManager.removeMemberById(groupId, memberId, IdentityZoneHolder.get().getId());
        return membership;
    }

    @ExceptionHandler
    public View handleException(Exception t, HttpServletRequest request) throws ScimException {
        ScimException e = new ScimException("Unexpected error", t, HttpStatus.INTERNAL_SERVER_ERROR);
        if (t instanceof ScimException) {
            e = (ScimException) t;
        } else {
            Class clazz = t.getClass();
            for (Class key : statuses.keySet()) {
                if (key.isAssignableFrom(clazz)) {
                    e = new ScimException(t.getMessage(), t, statuses.get(key));
                    break;
                }
            }
        }
        // User can supply trace=true or just trace (unspecified) to get stack
        // traces
        boolean trace = request.getParameter("trace") != null && !request.getParameter("trace").equals("false");
        return new ConvertingExceptionView(new ResponseEntity(new ExceptionReport(e, trace),
            e.getStatus()), messageConverters);
    }

    private int getVersion(String groupId, String etag) {
        String value = etag.trim();
        while (value.startsWith("\"")) {
            value = value.substring(1);
        }
        while (value.endsWith("\"")) {
            value = value.substring(0, value.length() - 1);
        }
        if (value.equals("*")) {
            return dao.retrieve(groupId, IdentityZoneHolder.get().getId()).getVersion();
        }
        try {
            return Integer.valueOf(value);
        } catch (NumberFormatException e) {
            throw new ScimException("Invalid version match header (should be a version number): " + etag,
                HttpStatus.BAD_REQUEST);
        }
    }

    private void addETagHeader(HttpServletResponse httpServletResponse, ScimGroup scimGroup) {
        httpServletResponse.setHeader(E_TAG, "\"" + scimGroup.getVersion() + "\"");
    }

    public void setGroupMaxCount(int groupMaxCount) {
        if (groupMaxCount <= 0) {
            throw new IllegalArgumentException(
                String.format("Invalid \"groupMaxCount\" value (got %d). Should be positive number.", groupMaxCount)
            );
        }
        this.groupMaxCount = groupMaxCount;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy