Search in sources :

Example 1 with PatchOperation

use of io.jans.scim.model.scim2.patch.PatchOperation in project oxTrust by GluuFederation.

the class BaseScimWebService method inspectPatchRequest.

protected Response inspectPatchRequest(PatchRequest patch, Class<? extends BaseScimResource> cls) {
    Response response = null;
    List<String> schemas = patch.getSchemas();
    if (schemas != null && schemas.size() == 1 && schemas.get(0).equals(PATCH_REQUEST_SCHEMA_ID)) {
        List<PatchOperation> ops = patch.getOperations();
        if (ops != null) {
            // Adjust paths if they came prefixed
            String defSchema = ScimResourceUtil.getDefaultSchemaUrn(cls);
            List<String> urns = extService.getUrnsOfExtensions(cls);
            urns.add(defSchema);
            for (PatchOperation op : ops) {
                if (op.getPath() != null)
                    op.setPath(ScimResourceUtil.adjustNotationInPath(op.getPath(), defSchema, urns));
            }
            for (PatchOperation op : ops) {
                if (op.getType() == null)
                    response = getErrorResponse(BAD_REQUEST, ErrorScimType.INVALID_SYNTAX, "Operation '" + op.getOperation() + "' not recognized");
                else {
                    String path = op.getPath();
                    if (StringUtils.isEmpty(path) && op.getType().equals(PatchOperationType.REMOVE))
                        response = getErrorResponse(BAD_REQUEST, ErrorScimType.NO_TARGET, "Path attribute is required for remove operation");
                    else if (op.getValue() == null && !op.getType().equals(PatchOperationType.REMOVE))
                        response = getErrorResponse(BAD_REQUEST, ErrorScimType.INVALID_SYNTAX, "Value attribute is required for operations other than remove");
                }
                if (response != null)
                    break;
            }
        } else
            response = getErrorResponse(BAD_REQUEST, ErrorScimType.INVALID_SYNTAX, "Patch request MUST contain the attribute 'Operations'");
    } else
        response = getErrorResponse(BAD_REQUEST, ErrorScimType.INVALID_SYNTAX, "Wrong schema(s) supplied in Search Request");
    log.info("inspectPatchRequest. Preprocessing of patch request {}", response == null ? "passed" : "failed");
    return response;
}
Also used : ListResponse(org.gluu.oxtrust.model.scim2.ListResponse) Response(javax.ws.rs.core.Response) ErrorResponse(org.gluu.oxtrust.model.scim2.ErrorResponse) PatchOperation(org.gluu.oxtrust.model.scim2.patch.PatchOperation)

Example 2 with PatchOperation

use of io.jans.scim.model.scim2.patch.PatchOperation in project oxTrust by GluuFederation.

the class GroupWebService method patchGroup.

@Path("{id}")
@PATCH
@Consumes({ MEDIA_TYPE_SCIM_JSON, MediaType.APPLICATION_JSON })
@Produces({ MEDIA_TYPE_SCIM_JSON + UTF8_CHARSET_FRAGMENT, MediaType.APPLICATION_JSON + UTF8_CHARSET_FRAGMENT })
@HeaderParam("Accept")
@DefaultValue(MEDIA_TYPE_SCIM_JSON)
@ProtectedApi
@RefAdjusted
@ApiOperation(value = "PATCH operation", notes = "https://tools.ietf.org/html/rfc7644#section-3.5.2", response = GroupResource.class)
public Response patchGroup(PatchRequest request, @PathParam("id") String id, @QueryParam(QUERY_PARAM_ATTRIBUTES) String attrsList, @QueryParam(QUERY_PARAM_EXCLUDED_ATTRS) String excludedAttrsList) {
    Response response;
    try {
        log.debug("Executing web service method. patchGroup");
        String usersUrl = userWebService.getEndpointUrl();
        GroupResource group = new GroupResource();
        // group is not null (check associated decorator method)
        GluuGroup gluuGroup = groupService.getGroupByInum(id);
        // Fill group instance with all info from gluuGroup
        scim2GroupService.transferAttributesToGroupResource(gluuGroup, group, endpointUrl, usersUrl);
        // Apply patches one by one in sequence
        for (PatchOperation po : request.getOperations()) group = (GroupResource) scim2PatchService.applyPatchOperation(group, po);
        // Throws exception if final representation does not pass overall validation
        log.debug("patchGroup. Revising final resource representation still passes validations");
        executeDefaultValidation(group);
        // Update timestamp
        String now = ISODateTimeFormat.dateTime().withZoneUTC().print(System.currentTimeMillis());
        group.getMeta().setLastModified(now);
        // Replaces the information found in gluuGroup with the contents of group
        scim2GroupService.replaceGroupInfo(gluuGroup, group, endpointUrl, usersUrl);
        String json = resourceSerializer.serialize(group, attrsList, excludedAttrsList);
        response = Response.ok(new URI(group.getMeta().getLocation())).entity(json).build();
    } catch (InvalidAttributeValueException e) {
        log.error(e.getMessage(), e);
        response = getErrorResponse(Response.Status.BAD_REQUEST, ErrorScimType.MUTABILITY, e.getMessage());
    } catch (SCIMException e) {
        response = getErrorResponse(Response.Status.BAD_REQUEST, ErrorScimType.INVALID_SYNTAX, e.getMessage());
    } catch (Exception e) {
        log.error("Failure at patchGroup method", e);
        response = getErrorResponse(Response.Status.INTERNAL_SERVER_ERROR, "Unexpected error: " + e.getMessage());
    }
    return response;
}
Also used : ListResponse(org.gluu.oxtrust.model.scim2.ListResponse) Response(javax.ws.rs.core.Response) ListViewResponse(org.gluu.persist.model.ListViewResponse) SCIMException(org.gluu.oxtrust.model.exception.SCIMException) PatchOperation(org.gluu.oxtrust.model.scim2.patch.PatchOperation) GluuGroup(org.gluu.oxtrust.model.GluuGroup) URI(java.net.URI) InvalidAttributeValueException(javax.management.InvalidAttributeValueException) GroupResource(org.gluu.oxtrust.model.scim2.group.GroupResource) SCIMException(org.gluu.oxtrust.model.exception.SCIMException) InvalidAttributeValueException(javax.management.InvalidAttributeValueException) Path(javax.ws.rs.Path) DefaultValue(javax.ws.rs.DefaultValue) HeaderParam(javax.ws.rs.HeaderParam) RefAdjusted(org.gluu.oxtrust.service.scim2.interceptor.RefAdjusted) Consumes(javax.ws.rs.Consumes) Produces(javax.ws.rs.Produces) ApiOperation(com.wordnik.swagger.annotations.ApiOperation) ProtectedApi(org.gluu.oxtrust.service.filter.ProtectedApi)

Example 3 with PatchOperation

use of io.jans.scim.model.scim2.patch.PatchOperation in project jans by JanssenProject.

the class GroupWebService method patchGroup.

@Path("{id}")
@PATCH
@Consumes({ MEDIA_TYPE_SCIM_JSON, MediaType.APPLICATION_JSON })
@Produces({ MEDIA_TYPE_SCIM_JSON + UTF8_CHARSET_FRAGMENT, MediaType.APPLICATION_JSON + UTF8_CHARSET_FRAGMENT })
@HeaderParam("Accept")
@DefaultValue(MEDIA_TYPE_SCIM_JSON)
@ProtectedApi(scopes = { "https://jans.io/scim/groups.write" })
@RefAdjusted
public Response patchGroup(PatchRequest request, @PathParam("id") String id, @QueryParam(QUERY_PARAM_ATTRIBUTES) String attrsList, @QueryParam(QUERY_PARAM_EXCLUDED_ATTRS) String excludedAttrsList) {
    Response response;
    try {
        log.debug("Executing web service method. patchGroup");
        response = inspectPatchRequest(request, GroupResource.class);
        if (response != null)
            return response;
        GluuGroup gluuGroup = groupService.getGroupByInum(id);
        if (gluuGroup == null)
            return notFoundResponse(id, groupResourceType);
        response = externalConstraintsService.applyEntityCheck(gluuGroup, request, httpHeaders, uriInfo, HttpMethod.PATCH, groupResourceType);
        if (response != null)
            return response;
        boolean skipValidation = isMembersValidationSkipped();
        boolean displayExcluded = isDisplayExcluded(skipValidation, attrsList, excludedAttrsList);
        GroupResource group = new GroupResource();
        // Fill group instance with all info from gluuGroup
        scim2GroupService.transferAttributesToGroupResource(gluuGroup, group, !skipValidation, endpointUrl, usersUrl);
        GroupResource original = (GroupResource) ScimResourceUtil.clone(group);
        Predicate<String> p = skipValidation ? selectionFilterSkipPredicate : (filter -> false);
        // Apply patches one by one in sequence
        for (PatchOperation po : request.getOperations()) {
            group = (GroupResource) scim2PatchService.applyPatchOperation(group, po, p);
        }
        log.debug("patchGroup. Revising final resource representation still passes validations");
        // Throws exception if final representation does not pass overall validation
        executeValidation(group);
        checkDisplayNameExistence(group.getDisplayName(), id);
        // Update timestamp
        group.getMeta().setLastModified(DateUtil.millisToISOString(System.currentTimeMillis()));
        if (!displayExcluded) {
            scim2GroupService.restoreMembersDisplay(original, group);
        }
        // Replaces the information found in gluuGroup with the contents of group
        scim2GroupService.replaceGroupInfo(gluuGroup, group, skipValidation, !displayExcluded, endpointUrl, usersUrl);
        String json = resourceSerializer.serialize(group, attrsList, excludedAttrsList);
        response = Response.ok(new URI(group.getMeta().getLocation())).entity(json).build();
    } catch (DuplicateEntryException e) {
        log.error(e.getMessage());
        response = getErrorResponse(Response.Status.CONFLICT, ErrorScimType.UNIQUENESS, e.getMessage());
    } catch (InvalidAttributeValueException e) {
        log.error(e.getMessage(), e);
        response = getErrorResponse(Response.Status.BAD_REQUEST, ErrorScimType.MUTABILITY, e.getMessage());
    } catch (SCIMException e) {
        response = getErrorResponse(Response.Status.BAD_REQUEST, ErrorScimType.INVALID_SYNTAX, e.getMessage());
    } catch (Exception e) {
        log.error("Failure at patchGroup method", e);
        response = getErrorResponse(Response.Status.INTERNAL_SERVER_ERROR, "Unexpected error: " + e.getMessage());
    }
    return response;
}
Also used : StringUtils(org.apache.commons.lang.StringUtils) Produces(javax.ws.rs.Produces) QUERY_PARAM_FILTER(io.jans.scim.model.scim2.Constants.QUERY_PARAM_FILTER) URISyntaxException(java.net.URISyntaxException) Path(javax.ws.rs.Path) QUERY_PARAM_SORT_ORDER(io.jans.scim.model.scim2.Constants.QUERY_PARAM_SORT_ORDER) BaseScimResource(io.jans.scim.model.scim2.BaseScimResource) MediaType(javax.ws.rs.core.MediaType) SCIMException(io.jans.scim.model.exception.SCIMException) QueryParam(javax.ws.rs.QueryParam) Consumes(javax.ws.rs.Consumes) DefaultValue(javax.ws.rs.DefaultValue) HeaderParam(javax.ws.rs.HeaderParam) GluuGroup(io.jans.scim.model.GluuGroup) PatchOperation(io.jans.scim.model.scim2.patch.PatchOperation) URI(java.net.URI) DELETE(javax.ws.rs.DELETE) SortOrder(io.jans.orm.model.SortOrder) Predicate(java.util.function.Predicate) PatchRequest(io.jans.scim.model.scim2.patch.PatchRequest) QUERY_PARAM_EXCLUDED_ATTRS(io.jans.scim.model.scim2.Constants.QUERY_PARAM_EXCLUDED_ATTRS) GroupResource(io.jans.scim.model.scim2.group.GroupResource) List(java.util.List) Response(javax.ws.rs.core.Response) ErrorScimType(io.jans.scim.model.scim2.ErrorScimType) Scim2PatchService(io.jans.scim.service.scim2.Scim2PatchService) PostConstruct(javax.annotation.PostConstruct) QUERY_PARAM_START_INDEX(io.jans.scim.model.scim2.Constants.QUERY_PARAM_START_INDEX) QUERY_PARAM_SORT_BY(io.jans.scim.model.scim2.Constants.QUERY_PARAM_SORT_BY) GroupService(io.jans.scim.service.GroupService) PathParam(javax.ws.rs.PathParam) QUERY_PARAM_COUNT(io.jans.scim.model.scim2.Constants.QUERY_PARAM_COUNT) GET(javax.ws.rs.GET) QUERY_PARAM_ATTRIBUTES(io.jans.scim.model.scim2.Constants.QUERY_PARAM_ATTRIBUTES) DuplicateEntryException(io.jans.orm.exception.operation.DuplicateEntryException) DateUtil(io.jans.scim.model.scim2.util.DateUtil) HttpMethod(javax.ws.rs.HttpMethod) ScimResourceUtil(io.jans.scim.model.scim2.util.ScimResourceUtil) Inject(javax.inject.Inject) Named(javax.inject.Named) POST(javax.ws.rs.POST) ProtectedApi(io.jans.scim.service.filter.ProtectedApi) UTF8_CHARSET_FRAGMENT(io.jans.scim.model.scim2.Constants.UTF8_CHARSET_FRAGMENT) RefAdjusted(io.jans.scim.service.scim2.interceptor.RefAdjusted) GROUP_OVERHEAD_BYPASS_PARAM(io.jans.scim.model.scim2.Constants.GROUP_OVERHEAD_BYPASS_PARAM) InvalidAttributeValueException(javax.management.InvalidAttributeValueException) SearchRequest(io.jans.scim.model.scim2.SearchRequest) PagedResult(io.jans.orm.model.PagedResult) MEDIA_TYPE_SCIM_JSON(io.jans.scim.model.scim2.Constants.MEDIA_TYPE_SCIM_JSON) Scim2GroupService(io.jans.scim.service.scim2.Scim2GroupService) PUT(javax.ws.rs.PUT) GluuGroup(io.jans.scim.model.GluuGroup) URI(java.net.URI) InvalidAttributeValueException(javax.management.InvalidAttributeValueException) GroupResource(io.jans.scim.model.scim2.group.GroupResource) URISyntaxException(java.net.URISyntaxException) SCIMException(io.jans.scim.model.exception.SCIMException) DuplicateEntryException(io.jans.orm.exception.operation.DuplicateEntryException) InvalidAttributeValueException(javax.management.InvalidAttributeValueException) Response(javax.ws.rs.core.Response) SCIMException(io.jans.scim.model.exception.SCIMException) PatchOperation(io.jans.scim.model.scim2.patch.PatchOperation) DuplicateEntryException(io.jans.orm.exception.operation.DuplicateEntryException) Path(javax.ws.rs.Path) DefaultValue(javax.ws.rs.DefaultValue) HeaderParam(javax.ws.rs.HeaderParam) RefAdjusted(io.jans.scim.service.scim2.interceptor.RefAdjusted) Consumes(javax.ws.rs.Consumes) Produces(javax.ws.rs.Produces) ProtectedApi(io.jans.scim.service.filter.ProtectedApi)

Example 4 with PatchOperation

use of io.jans.scim.model.scim2.patch.PatchOperation in project jans by JanssenProject.

the class BaseScimWebService method inspectPatchRequest.

protected Response inspectPatchRequest(PatchRequest patch, Class<? extends BaseScimResource> cls) {
    Response response = null;
    List<String> schemas = patch.getSchemas();
    if (schemas != null && schemas.size() == 1 && schemas.get(0).equals(PATCH_REQUEST_SCHEMA_ID)) {
        List<PatchOperation> ops = patch.getOperations();
        if (ops != null) {
            // Adjust paths if they came prefixed
            String defSchema = ScimResourceUtil.getDefaultSchemaUrn(cls);
            List<String> urns = extService.getUrnsOfExtensions(cls);
            urns.add(defSchema);
            for (PatchOperation op : ops) {
                if (op.getPath() != null)
                    op.setPath(ScimResourceUtil.adjustNotationInPath(op.getPath(), defSchema, urns));
            }
            for (PatchOperation op : ops) {
                if (op.getType() == null)
                    response = getErrorResponse(BAD_REQUEST, ErrorScimType.INVALID_SYNTAX, "Operation '" + op.getOperation() + "' not recognized");
                else {
                    String path = op.getPath();
                    if (StringUtils.isEmpty(path) && op.getType().equals(PatchOperationType.REMOVE))
                        response = getErrorResponse(BAD_REQUEST, ErrorScimType.NO_TARGET, "Path attribute is required for remove operation");
                    else if (op.getValue() == null && !op.getType().equals(PatchOperationType.REMOVE))
                        response = getErrorResponse(BAD_REQUEST, ErrorScimType.INVALID_SYNTAX, "Value attribute is required for operations other than remove");
                }
                if (response != null)
                    break;
            }
        } else
            response = getErrorResponse(BAD_REQUEST, ErrorScimType.INVALID_SYNTAX, "Patch request MUST contain the attribute 'Operations'");
    } else
        response = getErrorResponse(BAD_REQUEST, ErrorScimType.INVALID_SYNTAX, "Wrong schema(s) supplied in Search Request");
    log.info("inspectPatchRequest. Preprocessing of patch request {}", response == null ? "passed" : "failed");
    return response;
}
Also used : ErrorResponse(io.jans.scim.model.scim2.ErrorResponse) Response(javax.ws.rs.core.Response) ListResponse(io.jans.scim.model.scim2.ListResponse) PatchOperation(io.jans.scim.model.scim2.patch.PatchOperation)

Example 5 with PatchOperation

use of io.jans.scim.model.scim2.patch.PatchOperation in project jans by JanssenProject.

the class Scim2PatchService method applyPatchOperationWithValueFilter.

private BaseScimResource applyPatchOperationWithValueFilter(BaseScimResource resource, PatchOperation operation, String valSelFilter, String attribute, String subAttribute) throws SCIMException, InvalidAttributeValueException {
    String path = operation.getPath();
    ObjectMapper mapper = new ObjectMapper();
    Class<? extends BaseScimResource> cls = resource.getClass();
    Map<String, Object> resourceAsMap = mapper.convertValue(resource, new TypeReference<Map<String, Object>>() {
    });
    List<Map<String, Object>> list;
    Attribute attrAnnot = IntrospectUtil.getFieldAnnotation(attribute, cls, Attribute.class);
    if (attrAnnot != null) {
        if (!attrAnnot.multiValueClass().equals(NullType.class) && attrAnnot.type().equals(AttributeDefinition.Type.COMPLEX)) {
            Object colObject = resourceAsMap.get(attribute);
            list = colObject == null ? null : new ArrayList<>((Collection<Map<String, Object>>) colObject);
        } else {
            throw new SCIMException(String.format("Attribute '%s' expected to be complex multi-valued", attribute));
        }
    } else {
        throw new SCIMException(String.format("Attribute '%s' not recognized or expected to be complex multi-valued", attribute));
    }
    if (list == null) {
        log.info("applyPatchOperationWithValueFilter. List of values for {} is empty. Operation has no effect", attribute);
    } else {
        try {
            valSelFilter = FilterUtil.preprocess(valSelFilter, cls);
            ParseTree parseTree = filterService.getParseTree(valSelFilter);
            List<Integer> matchingIndexes = new ArrayList<>();
            for (int i = 0; i < list.size(); i++) {
                if (filterService.complexAttributeMatch(parseTree, list.get(i), attribute, cls)) {
                    // Important: add so that resulting list is reverse-ordered
                    matchingIndexes.add(0, i);
                }
            }
            if (subAttribute.length() > 0 && matchingIndexes.size() > 0 && operation.getType().equals(PatchOperationType.REMOVE)) {
                // per spec (section 3.5.2.2 RFC 7644) subAttribute must not be required or read-only
                Attribute subAttrAnnot = IntrospectUtil.getFieldAnnotation(attribute + "." + subAttribute, cls, Attribute.class);
                if (subAttrAnnot != null && (subAttrAnnot.mutability().equals(READ_ONLY) || subAttrAnnot.isRequired())) {
                    throw new InvalidAttributeValueException("Cannot remove read-only or required attribute " + attribute + "." + subAttribute);
                }
            }
            /*
                Here we differ from spec (see section 3.5.2.3/4 of RFC7644. If no record match is made, we are supposed to
                return error 400 with scimType of noTarget. But this is clearly inconvenient
                */
            log.info("There are {} entries matching the filter '{}'", matchingIndexes.size(), path);
            for (Integer index : matchingIndexes) {
                if (operation.getType().equals(PatchOperationType.REMOVE)) {
                    if (subAttribute.length() == 0) {
                        // Remove the whole item
                        // If intValue is not used, the remove(Object) method is called!
                        list.remove(index.intValue());
                    } else {
                        // remove subattribute only
                        list.get(index).remove(subAttribute);
                    }
                } else {
                    applyPartialUpdate(attribute, subAttribute, list, index, operation.getValue(), cls);
                }
            }
            log.trace("New {} list is:\n{}", attribute, mapper.writeValueAsString(list));
            resourceAsMap.put(attribute, list.isEmpty() ? null : list);
            resource = mapper.convertValue(resourceAsMap, cls);
        } catch (InvalidAttributeValueException ei) {
            throw ei;
        } catch (Exception e) {
            log.info("Error processing Patch operation with value selection path '{}'", path);
            log.error(e.getMessage(), e);
            throw new SCIMException(e.getMessage(), e);
        }
    }
    return resource;
}
Also used : Attribute(io.jans.scim.model.scim2.annotations.Attribute) ArrayList(java.util.ArrayList) InvalidAttributeValueException(javax.management.InvalidAttributeValueException) SCIMException(io.jans.scim.model.exception.SCIMException) InvalidAttributeValueException(javax.management.InvalidAttributeValueException) SCIMException(io.jans.scim.model.exception.SCIMException) HashMap(java.util.HashMap) Map(java.util.Map) ObjectMapper(com.fasterxml.jackson.databind.ObjectMapper) ParseTree(org.antlr.v4.runtime.tree.ParseTree)

Aggregations

Response (javax.ws.rs.core.Response)13 PatchOperation (io.jans.scim.model.scim2.patch.PatchOperation)10 PatchRequest (io.jans.scim.model.scim2.patch.PatchRequest)8 Test (org.testng.annotations.Test)7 UserResource (io.jans.scim.model.scim2.user.UserResource)6 InvalidAttributeValueException (javax.management.InvalidAttributeValueException)5 SCIMException (io.jans.scim.model.exception.SCIMException)4 BaseTest (io.jans.scim2.client.BaseTest)4 URI (java.net.URI)4 Consumes (javax.ws.rs.Consumes)4 DefaultValue (javax.ws.rs.DefaultValue)4 HeaderParam (javax.ws.rs.HeaderParam)4 Path (javax.ws.rs.Path)4 Produces (javax.ws.rs.Produces)4 GroupResource (io.jans.scim.model.scim2.group.GroupResource)3 ArrayList (java.util.ArrayList)3 ListResponse (org.gluu.oxtrust.model.scim2.ListResponse)3 ObjectMapper (com.fasterxml.jackson.databind.ObjectMapper)2 ApiOperation (com.wordnik.swagger.annotations.ApiOperation)2 DuplicateEntryException (io.jans.orm.exception.operation.DuplicateEntryException)2