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

com.unboundid.scim.wink.AbstractSCIMResource Maven / Gradle / Ivy

/*
 * Copyright 2011-2019 Ping Identity Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (GPLv2 only)
 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see .
 */

package com.unboundid.scim.wink;

import com.unboundid.scim.data.BaseResource;
import com.unboundid.scim.marshal.Unmarshaller;
import com.unboundid.scim.marshal.json.JsonUnmarshaller;
import com.unboundid.scim.marshal.xml.XmlUnmarshaller;
import com.unboundid.scim.schema.ResourceDescriptor;
import com.unboundid.scim.sdk.AttributePath;
import com.unboundid.scim.sdk.Debug;
import com.unboundid.scim.sdk.DebugType;
import com.unboundid.scim.sdk.DeleteResourceRequest;
import com.unboundid.scim.sdk.ForbiddenException;
import com.unboundid.scim.sdk.GetResourceRequest;
import com.unboundid.scim.sdk.GetResourcesRequest;
import com.unboundid.scim.sdk.InvalidResourceException;
import com.unboundid.scim.sdk.NotModifiedException;
import com.unboundid.scim.sdk.OAuthToken;
import com.unboundid.scim.sdk.OAuthTokenHandler;
import com.unboundid.scim.sdk.OAuthTokenStatus;
import com.unboundid.scim.sdk.PageParameters;
import com.unboundid.scim.sdk.PatchResourceRequest;
import com.unboundid.scim.sdk.PostResourceRequest;
import com.unboundid.scim.sdk.PreconditionFailedException;
import com.unboundid.scim.sdk.PutResourceRequest;
import com.unboundid.scim.sdk.ResourceNotFoundException;
import com.unboundid.scim.sdk.ResourceSchemaBackend;
import com.unboundid.scim.sdk.Resources;
import com.unboundid.scim.sdk.SCIMBackend;
import com.unboundid.scim.sdk.SCIMException;
import com.unboundid.scim.sdk.SCIMFilter;
import com.unboundid.scim.sdk.SCIMQueryAttributes;
import com.unboundid.scim.sdk.SCIMRequest;
import com.unboundid.scim.sdk.SortParameters;
import com.unboundid.scim.sdk.UnauthorizedException;


import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;

import static com.unboundid.scim.sdk.SCIMConstants.*;



/**
 * This class is an abstract Wink resource implementation for
 * SCIM operations on a SCIM endpoint.
 */
public abstract class AbstractSCIMResource extends AbstractStaticResource
{
  private final SCIMApplication application;

  /**
   * The set of request parameters supported by search requests, all in lower
   * case.
   */
  private static final Set SEARCH_REQUEST_PARAMS;
  static
  {
    final Set params = new TreeSet();
    params.add(QUERY_PARAMETER_ATTRIBUTES);
    params.add(QUERY_PARAMETER_FILTER);
    params.add(QUERY_PARAMETER_BASE_ID);
    params.add(QUERY_PARAMETER_SCOPE);
    params.add(QUERY_PARAMETER_SORT_BY_LC);
    params.add(QUERY_PARAMETER_SORT_ORDER_LC);
    params.add(QUERY_PARAMETER_PAGE_START_INDEX_LC);
    params.add(QUERY_PARAMETER_PAGE_SIZE);
    SEARCH_REQUEST_PARAMS = Collections.unmodifiableSet(params);
  }

  /**
   * The set of request parameters supported by any request, all in lower
   * case.
   */
  private static final Set COMMON_REQUEST_PARAMS;
  static
  {
    final Set params = new TreeSet();
    params.add(QUERY_PARAMETER_ATTRIBUTES);
    COMMON_REQUEST_PARAMS = Collections.unmodifiableSet(params);
  }

  /**
   * The OAuth 2.0 bearer token handler. This may be null.
   */
  private final OAuthTokenHandler tokenHandler;

  private final ResourceSchemaBackend resourceSchemaBackend;

  /**
   * Create a new AbstractSCIMResource for CRUD operations.
   *
   * @param application         The SCIM JAX-RS application associated with this
   *                            resource.
   * @param tokenHandler        The token handler to use for OAuth
   *                            authentication.
   */
  public AbstractSCIMResource(final SCIMApplication application,
                              final OAuthTokenHandler tokenHandler)
  {
    this.application = application;
    this.tokenHandler = tokenHandler;
    this.resourceSchemaBackend = new ResourceSchemaBackend(application);
  }



  /**
   * Process a GET operation.
   *
   * @param requestContext The request context.
   * @param endpoint       The endpoint requested.
   * @param userID         The user ID requested.
   *
   * @return  The response to the operation.
   */
  Response getUser(final RequestContext requestContext,
                   final String endpoint, final String userID)
  {
    logIgnoredQueryParams(requestContext, COMMON_REQUEST_PARAMS);

    SCIMBackend backend;
    ResourceDescriptor resourceDescriptor = null;
    Response.ResponseBuilder responseBuilder;
    try {
      backend = getBackend(endpoint);
      resourceDescriptor = backend.getResourceDescriptor(endpoint);
      if(resourceDescriptor == null)
      {
        throw new ResourceNotFoundException(
                endpoint + " is not a valid resource endpoint");
      }
      String authID = requestContext.getAuthID();
      if(authID == null && tokenHandler == null) {
        throw new UnauthorizedException("Invalid credentials");
      }
      final String attributes =
          requestContext.getUriInfo().getQueryParameters().getFirst(
              QUERY_PARAMETER_ATTRIBUTES);
      final SCIMQueryAttributes queryAttributes =
          new SCIMQueryAttributes(resourceDescriptor, attributes);

      // Process the request.
      GetResourceRequest getResourceRequest =
          new GetResourceRequest(requestContext.getUriInfo().getBaseUri(),
              authID, resourceDescriptor, userID, queryAttributes,
              requestContext.getRequest());

      if (authID == null)
      {
        AtomicReference authIDRef = new AtomicReference();
        Response response = validateOAuthToken(requestContext,
                              getResourceRequest, authIDRef, tokenHandler);
        if (response != null)
        {
          application.getStatsForResource(resourceDescriptor.getName()).
              incrementStat("get-" + response.getStatus());
          return response;
        }
        else
        {
          authID = authIDRef.get();
          getResourceRequest =
               new GetResourceRequest(requestContext.getUriInfo().getBaseUri(),
                   authID, resourceDescriptor, userID, queryAttributes,
                   requestContext.getRequest());
        }
      }

      BaseResource resource =
          backend.getResource(getResourceRequest);

      // Build the response.
      responseBuilder = Response.status(Response.Status.OK);
        setResponseEntity(responseBuilder, requestContext.getProduceMediaType(),
            resource);
        application.getStatsForResource(resourceDescriptor.getName()).
          incrementStat(ResourceStats.GET_OK);
      responseBuilder.contentLocation(resource.getMeta().getLocation());
      // cant use responsebuilder.tag ... it will quote the
      // already quoted string
      responseBuilder.header(HttpHeaders.ETAG, resource.getMeta().getVersion());

      if(requestContext.getProduceMediaType() ==
          MediaType.APPLICATION_JSON_TYPE)
      {
        application.getStatsForResource(resourceDescriptor.getName()).
            incrementStat(ResourceStats.GET_RESPONSE_JSON);
      }
      else if(requestContext.getProduceMediaType() ==
              MediaType.APPLICATION_XML_TYPE)
      {
        application.getStatsForResource(resourceDescriptor.getName()).
            incrementStat(ResourceStats.GET_RESPONSE_XML);
      }
    } catch (SCIMException e) {
      Debug.debugException(e);
      responseBuilder = error(e, requestContext);
      if(resourceDescriptor != null)
      {
        application.getStatsForResource(resourceDescriptor.getName()).
            incrementStat("get-" + e.getStatusCode());
      }
    }

    return responseBuilder.build();
  }



  /**
   * Process a GET operation.
   *
   * @param requestContext   The request context.
   * @param endpoint         The endpoint requested.
   * @param filterString     The filter query parameter, or {@code null}.
   * @param baseID           The SCIM resource ID of the search base entry,
   *                         or {@code null}.
   * @param searchScope      The LDAP search scope to use, or {@code null}.
   * @param sortBy           The sortBy query parameter, or {@code null}.
   * @param sortOrder        The sortOrder query parameter, or {@code null}.
   * @param pageStartIndex   The startIndex query parameter, or {@code null}.
   * @param pageSize         The count query parameter, or {@code null}.
   *
   * @return  The response to the operation.
   */
  protected Response getUsers(final RequestContext requestContext,
                              final String endpoint,
                              final String filterString,
                              final String baseID,
                              final String searchScope,
                              final String sortBy,
                              final String sortOrder,
                              final String pageStartIndex,
                              final String pageSize)
  {
    logIgnoredQueryParams(requestContext, SEARCH_REQUEST_PARAMS);

    SCIMBackend backend;
    ResourceDescriptor resourceDescriptor = null;
    Response.ResponseBuilder responseBuilder;
    try
    {
      backend = getBackend(endpoint);
      resourceDescriptor = backend.getResourceDescriptor(endpoint);
      if(resourceDescriptor == null)
      {
        throw new ResourceNotFoundException(
                endpoint + " is not a valid resource endpoint");
      }
      String authID = requestContext.getAuthID();
      if(authID == null && tokenHandler == null) {
        throw new UnauthorizedException("Invalid credentials");
      }
      final String attributes =
          requestContext.getUriInfo().getQueryParameters().getFirst(
              QUERY_PARAMETER_ATTRIBUTES);
      final SCIMQueryAttributes queryAttributes =
          new SCIMQueryAttributes(resourceDescriptor, attributes);

      // Parse the filter parameters.
      final SCIMFilter filter = parseFilter(filterString, resourceDescriptor);

      // Parse the sort parameters.
      final SortParameters sortParameters;
      if (sortBy != null && !sortBy.isEmpty())
      {
        sortParameters =
            new SortParameters(AttributePath.parse(sortBy,
                                resourceDescriptor.getSchema()), sortOrder);
      }
      else
      {
        sortParameters = null;
      }

      // Parse the pagination parameters.
      int startIndex = -1;
      int count = -1;
      if (pageStartIndex != null && !pageStartIndex.isEmpty())
      {
        try
        {
          startIndex = Integer.parseInt(pageStartIndex);
        }
        catch (NumberFormatException e)
        {
          Debug.debugException(e);
          throw new InvalidResourceException(
              "The pagination startIndex value '" + pageStartIndex +
              "' is not parsable");
        }

        if (startIndex <= 0)
        {
          throw new InvalidResourceException(
              "The pagination startIndex value '" + pageStartIndex +
              "' is invalid because it is not greater than zero");
        }
      }
      if (pageSize != null && !pageSize.isEmpty())
      {
        try
        {
          count = Integer.parseInt(pageSize);
        }
        catch (NumberFormatException e)
        {
          Debug.debugException(e);
          throw new InvalidResourceException(
              "The pagination count value '" + pageSize +
              "' is not parsable");
        }

        if (count <= 0)
        {
          throw new InvalidResourceException(
              "The pagination count value '" + pageSize +
              "' is invalid because it is not greater than zero");
        }
      }

      final PageParameters pageParameters;
      if (startIndex >= 0 && count >= 0)
      {
        pageParameters = new PageParameters(startIndex, count);
      }
      else if (startIndex >= 0)
      {
        pageParameters = new PageParameters(startIndex, 0);
      }
      else if (count >= 0)
      {
        pageParameters = new PageParameters(1, count);
      }
      else
      {
        pageParameters = null;
      }

      // Process the request.
      GetResourcesRequest getResourcesRequest =
          new GetResourcesRequest(requestContext.getUriInfo().getBaseUri(),
              authID, resourceDescriptor, filter, baseID, searchScope,
              sortParameters, pageParameters, queryAttributes,
              requestContext.getRequest());

      if (authID == null)
      {
        AtomicReference authIDRef = new AtomicReference();
        Response response = validateOAuthToken(requestContext,
                              getResourcesRequest, authIDRef, tokenHandler);
        if (response != null)
        {
          application.getStatsForResource(resourceDescriptor.getName()).
              incrementStat("query-" + response.getStatus());
          return response;
        }
        else
        {
          authID = authIDRef.get();
          getResourcesRequest =
              new GetResourcesRequest(requestContext.getUriInfo().getBaseUri(),
                      authID, resourceDescriptor, filter, baseID, searchScope,
                      sortParameters, pageParameters, queryAttributes,
                      requestContext.getRequest());
        }
      }

      final Resources resources = backend.getResources(getResourcesRequest);

      // Build the response.
      responseBuilder =
          Response.status(Response.Status.OK);
      setResponseEntity(responseBuilder, requestContext.getProduceMediaType(),
                        resources);

      application.getStatsForResource(resourceDescriptor.getName()).
          incrementStat(ResourceStats.QUERY_OK);
      if(requestContext.getProduceMediaType() ==
          MediaType.APPLICATION_JSON_TYPE)
      {
        application.getStatsForResource(resourceDescriptor.getName()).
            incrementStat(ResourceStats.QUERY_RESPONSE_JSON);
      }
      else if(requestContext.getProduceMediaType() ==
              MediaType.APPLICATION_XML_TYPE)
      {
        application.getStatsForResource(resourceDescriptor.getName()).
            incrementStat(ResourceStats.QUERY_RESPONSE_XML);
      }
    }
    catch(SCIMException e)
    {
      responseBuilder =
          Response.status(e.getStatusCode());
      setResponseEntity(responseBuilder, requestContext.getProduceMediaType(),
                        e);
      if(resourceDescriptor != null)
      {
        application.getStatsForResource(resourceDescriptor.getName()).
            incrementStat("query-" + e.getStatusCode());
      }
    }

    return responseBuilder.build();
  }


  /**
   * Process a POST operation.
   *
   * @param requestContext    The request context.
   * @param endpoint       The endpoint requested.
   * @param inputStream       The content to be consumed.
   *
   * @return  The response to the operation.
   */
  Response postUser(final RequestContext requestContext,
                    final String endpoint,
                    final InputStream inputStream)
  {
    logIgnoredQueryParams(requestContext, COMMON_REQUEST_PARAMS);

    SCIMBackend backend;
    ResourceDescriptor resourceDescriptor = null;
    Response.ResponseBuilder responseBuilder;
    try
    {
      backend = getBackend(endpoint);
      resourceDescriptor = backend.getResourceDescriptor(endpoint);
      if(resourceDescriptor == null)
      {
        throw new ResourceNotFoundException(
                endpoint + " is not a valid resource endpoint");
      }
      final Unmarshaller unmarshaller;
      if (requestContext.getConsumeMediaType().equals(
          MediaType.APPLICATION_JSON_TYPE))
      {
        unmarshaller = new JsonUnmarshaller();
        application.getStatsForResource(resourceDescriptor.getName()).
            incrementStat(ResourceStats.POST_CONTENT_JSON);
      }
      else
      {
        unmarshaller = new XmlUnmarshaller();
        application.getStatsForResource(resourceDescriptor.getName()).
            incrementStat(ResourceStats.POST_CONTENT_XML);
      }
      String authID = requestContext.getAuthID();
      if(authID == null && tokenHandler == null)
      {
        throw new UnauthorizedException("Invalid credentials");
      }

      // Parse the resource.
      final BaseResource postedResource = unmarshaller.unmarshal(
          inputStream, resourceDescriptor, BaseResource.BASE_RESOURCE_FACTORY);

      final String attributes =
          requestContext.getUriInfo().getQueryParameters().getFirst(
              QUERY_PARAMETER_ATTRIBUTES);
      final SCIMQueryAttributes queryAttributes =
          new SCIMQueryAttributes(resourceDescriptor, attributes);

      // Process the request.
      PostResourceRequest postResourceRequest =
          new PostResourceRequest(requestContext.getUriInfo().getBaseUri(),
              authID, resourceDescriptor, postedResource.getScimObject(),
              queryAttributes, requestContext.getRequest());

      if (authID == null)
      {
        AtomicReference authIDRef = new AtomicReference();
        Response response = validateOAuthToken(requestContext,
                              postResourceRequest, authIDRef, tokenHandler);
        if (response != null)
        {
          application.getStatsForResource(resourceDescriptor.getName()).
              incrementStat("post-" + response.getStatus());
          return response;
        }
        else
        {
          authID = authIDRef.get();
          postResourceRequest =
              new PostResourceRequest(requestContext.getUriInfo().getBaseUri(),
                    authID, resourceDescriptor, postedResource.getScimObject(),
                    queryAttributes, requestContext.getRequest());
        }
      }

      final BaseResource resource = backend.postResource(postResourceRequest);
      // Build the response.
      responseBuilder = Response.status(Response.Status.CREATED);
      setResponseEntity(responseBuilder, requestContext.getProduceMediaType(),
          resource);
      responseBuilder.location(resource.getMeta().getLocation());
      // cant use responsebuilder.tag ... it will quote the already
      // quoted string
      responseBuilder.header(HttpHeaders.ETAG, resource.getMeta().getVersion());
      application.getStatsForResource(resourceDescriptor.getName()).
          incrementStat(ResourceStats.POST_OK);
      if(requestContext.getProduceMediaType() ==
          MediaType.APPLICATION_JSON_TYPE)
      {
        application.getStatsForResource(resourceDescriptor.getName()).
            incrementStat(ResourceStats.POST_RESPONSE_JSON);
      }
      else if(requestContext.getProduceMediaType() ==
              MediaType.APPLICATION_XML_TYPE)
      {
        application.getStatsForResource(resourceDescriptor.getName()).
            incrementStat(ResourceStats.POST_RESPONSE_XML);
      }
    } catch (SCIMException e) {
      Debug.debugException(e);
      responseBuilder = error(e, requestContext);
      if(resourceDescriptor != null)
      {
        application.getStatsForResource(resourceDescriptor.getName()).
            incrementStat("post-" + e.getStatusCode());
      }
    }

    return responseBuilder.build();
  }



  /**
   * Process a PUT operation.
   *
   * @param requestContext    The request context.
   * @param endpoint          The endpoint requested.
   * @param userID            The target user ID.
   * @param inputStream       The content to be consumed.
   *
   * @return  The response to the operation.
   */
  Response putUser(final RequestContext requestContext,
                   final String endpoint,
                   final String userID,
                   final InputStream inputStream)
  {
    logIgnoredQueryParams(requestContext, COMMON_REQUEST_PARAMS);

    SCIMBackend backend;
    ResourceDescriptor resourceDescriptor = null;
    Response.ResponseBuilder responseBuilder;
    try {
      backend = getBackend(endpoint);
      resourceDescriptor = backend.getResourceDescriptor(endpoint);
      if(resourceDescriptor == null)
      {
        throw new ResourceNotFoundException(
                endpoint + " is not a valid resource endpoint");
      }
      final Unmarshaller unmarshaller;
      if (requestContext.getConsumeMediaType().equals(
          MediaType.APPLICATION_JSON_TYPE))
      {
        unmarshaller = new JsonUnmarshaller();
        application.getStatsForResource(resourceDescriptor.getName()).
            incrementStat(ResourceStats.PUT_CONTENT_JSON);
      }
      else
      {
        unmarshaller = new XmlUnmarshaller();
        application.getStatsForResource(resourceDescriptor.getName()).
            incrementStat(ResourceStats.PUT_CONTENT_XML);
      }
      String authID = requestContext.getAuthID();
      if(authID == null && tokenHandler == null)
      {
        throw new UnauthorizedException("Invalid credentials");
      }

      // Parse the resource.
      final BaseResource puttedResource = unmarshaller.unmarshal(
          inputStream, resourceDescriptor, BaseResource.BASE_RESOURCE_FACTORY);

      final String attributes =
          requestContext.getUriInfo().getQueryParameters().getFirst(
              QUERY_PARAMETER_ATTRIBUTES);
      final SCIMQueryAttributes queryAttributes =
          new SCIMQueryAttributes(resourceDescriptor, attributes);

      // Process the request.
      PutResourceRequest putResourceRequest =
          new PutResourceRequest(requestContext.getUriInfo().getBaseUri(),
              authID, resourceDescriptor, userID,
              puttedResource.getScimObject(), queryAttributes,
              requestContext.getRequest());

      if (authID == null)
      {
        AtomicReference authIDRef = new AtomicReference();
        Response response = validateOAuthToken(requestContext,
                              putResourceRequest, authIDRef, tokenHandler);
        if (response != null)
        {
          application.getStatsForResource(resourceDescriptor.getName()).
              incrementStat("put-" + response.getStatus());
          return response;
        }
        else
        {
          authID = authIDRef.get();
          putResourceRequest =
             new PutResourceRequest(requestContext.getUriInfo().getBaseUri(),
                  authID, resourceDescriptor, userID,
                  puttedResource.getScimObject(), queryAttributes,
                  requestContext.getRequest());
        }
      }

      final BaseResource scimResponse = backend.putResource(putResourceRequest);
      // Build the response.
      responseBuilder = Response.status(Response.Status.OK);
      setResponseEntity(responseBuilder, requestContext.getProduceMediaType(),
                        scimResponse);
      responseBuilder.contentLocation(scimResponse.getMeta().getLocation());
      // cant use responsebuilder.tag ... it will quote the already
      // quoted string
      responseBuilder.header(HttpHeaders.ETAG,
          scimResponse.getMeta().getVersion());
      application.getStatsForResource(resourceDescriptor.getName()).
          incrementStat(ResourceStats.PUT_OK);
      if(requestContext.getProduceMediaType() ==
          MediaType.APPLICATION_JSON_TYPE)
      {
        application.getStatsForResource(resourceDescriptor.getName()).
            incrementStat(ResourceStats.PUT_RESPONSE_JSON);
      }
      else if(requestContext.getProduceMediaType() ==
              MediaType.APPLICATION_XML_TYPE)
      {
        application.getStatsForResource(resourceDescriptor.getName()).
            incrementStat(ResourceStats.PUT_RESPONSE_XML);
      }
    } catch (SCIMException e) {
      Debug.debugException(e);
      responseBuilder = error(e, requestContext);
      if(resourceDescriptor != null)
      {
        application.getStatsForResource(resourceDescriptor.getName()).
            incrementStat("put-" + e.getStatusCode());
      }
    }

    return responseBuilder.build();
  }



  /**
   * Process a PATCH operation.
   *
   * @param requestContext    The request context.
   * @param endpoint          The endpoint requested.
   * @param userID            The target user ID.
   * @param inputStream       The content to be consumed.
   *
   * @return  The response to the operation.
   */
  Response patchUser(final RequestContext requestContext,
                     final String endpoint,
                     final String userID,
                     final InputStream inputStream)
  {
    logIgnoredQueryParams(requestContext, COMMON_REQUEST_PARAMS);

    SCIMBackend backend;
    ResourceDescriptor resourceDescriptor = null;
    Response.ResponseBuilder responseBuilder;
    try {
      backend = getBackend(endpoint);
      resourceDescriptor = backend.getResourceDescriptor(endpoint);
      if(resourceDescriptor == null)
      {
        throw new ResourceNotFoundException(
                endpoint + " is not a valid resource endpoint");
      }
      String authID = requestContext.getAuthID();
      if(authID == null && tokenHandler == null)
      {
        throw new UnauthorizedException("Invalid credentials");
      }
      final Unmarshaller unmarshaller;
      if (requestContext.getConsumeMediaType().equals(
              MediaType.APPLICATION_JSON_TYPE))
      {
        unmarshaller = new JsonUnmarshaller();
        application.getStatsForResource(resourceDescriptor.getName()).
            incrementStat(ResourceStats.PATCH_CONTENT_JSON);
      }
      else
      {
        unmarshaller = new XmlUnmarshaller();
        application.getStatsForResource(resourceDescriptor.getName()).
            incrementStat(ResourceStats.PATCH_CONTENT_XML);
      }
      // Parse the resource.
      final BaseResource patchedResource = unmarshaller.unmarshal(
           inputStream, resourceDescriptor, BaseResource.BASE_RESOURCE_FACTORY);

      final String attributes =
              requestContext.getUriInfo().getQueryParameters().getFirst(
                      QUERY_PARAMETER_ATTRIBUTES);
      final SCIMQueryAttributes queryAttributes =
              new SCIMQueryAttributes(resourceDescriptor, attributes);

      // Process the request.
      PatchResourceRequest patchResourceRequest =
              new PatchResourceRequest(requestContext.getUriInfo().getBaseUri(),
                      authID, resourceDescriptor, userID,
                      patchedResource.getScimObject(),
                      queryAttributes, requestContext.getRequest());

      if (authID == null)
      {
        AtomicReference authIDRef = new AtomicReference();
        Response response = validateOAuthToken(requestContext,
                              patchResourceRequest, authIDRef, tokenHandler);
        if (response != null)
        {
          application.getStatsForResource(resourceDescriptor.getName()).
              incrementStat("patch-" + response.getStatus());
          return response;
        }
        else
        {
          authID = authIDRef.get();
          patchResourceRequest =
             new PatchResourceRequest(requestContext.getUriInfo().getBaseUri(),
                   authID, resourceDescriptor, userID,
                   patchedResource.getScimObject(), queryAttributes,
                   requestContext.getRequest());
        }
      }

      final BaseResource scimResponse =
              backend.patchResource(patchResourceRequest);

      // Build the response.
      if (!queryAttributes.allAttributesRequested())
      {
        responseBuilder = Response.status(Response.Status.OK);
        setResponseEntity(responseBuilder, requestContext.getProduceMediaType(),
                scimResponse);
      }
      else
      {
        responseBuilder = Response.status(Response.Status.NO_CONTENT);
      }
      responseBuilder.contentLocation(scimResponse.getMeta().getLocation());
      // cant use responsebuilder.tag ... it will quote the already
      // quoted string
      responseBuilder.header(HttpHeaders.ETAG,
          scimResponse.getMeta().getVersion());

      application.getStatsForResource(resourceDescriptor.getName()).
          incrementStat(ResourceStats.PATCH_OK);
      if(requestContext.getProduceMediaType() ==
          MediaType.APPLICATION_JSON_TYPE)
      {
        application.getStatsForResource(resourceDescriptor.getName()).
            incrementStat(ResourceStats.PATCH_RESPONSE_JSON);
      }
      else if(requestContext.getProduceMediaType() ==
              MediaType.APPLICATION_XML_TYPE)
      {
        application.getStatsForResource(resourceDescriptor.getName()).
            incrementStat(ResourceStats.PATCH_RESPONSE_XML);
      }
    } catch (SCIMException e) {
      Debug.debugException(e);
      responseBuilder = error(e, requestContext);
      if(resourceDescriptor != null)
      {
        application.getStatsForResource(resourceDescriptor.getName()).
            incrementStat("patch-" + e.getStatusCode());
      }
    }

    return responseBuilder.build();
  }



  /**
   * Process a DELETE operation.
   *
   * @param requestContext    The request context.
   * @param endpoint          The endpoint requested.
   * @param userID            The target user ID.
   *
   * @return  The response to the operation.
   */
  Response deleteUser(final RequestContext requestContext,
                      final String endpoint,
                      final String userID)
  {
    SCIMBackend backend;
    ResourceDescriptor resourceDescriptor = null;
    // Process the request.
    Response.ResponseBuilder responseBuilder;
    try {
      backend = getBackend(endpoint);
      resourceDescriptor = backend.getResourceDescriptor(endpoint);
      if(resourceDescriptor == null)
      {
        throw new ResourceNotFoundException(
                endpoint + " is not a valid resource endpoint");
      }
      String authID = requestContext.getAuthID();
      if(authID == null && tokenHandler == null)
      {
        throw new UnauthorizedException("Invalid credentials");
      }

      DeleteResourceRequest deleteResourceRequest =
        new DeleteResourceRequest(requestContext.getUriInfo().getBaseUri(),
            authID, resourceDescriptor, userID,
            requestContext.getRequest());

      if (authID == null)
      {
        AtomicReference authIDRef = new AtomicReference();
        Response response = validateOAuthToken(requestContext,
                              deleteResourceRequest, authIDRef, tokenHandler);
        if (response != null)
        {
          application.getStatsForResource(resourceDescriptor.getName()).
              incrementStat("delete-" + response.getStatus());
          return response;
        }
        else
        {
          authID = authIDRef.get();
          deleteResourceRequest =
             new DeleteResourceRequest(requestContext.getUriInfo().getBaseUri(),
                   authID, resourceDescriptor, userID,
                   requestContext.getRequest());
        }
      }

      backend.deleteResource(deleteResourceRequest);
      // Build the response.
      responseBuilder = Response.status(Response.Status.OK);
      application.getStatsForResource(resourceDescriptor.getName()).
          incrementStat(ResourceStats.DELETE_OK);
    } catch (SCIMException e) {
      Debug.debugException(e);
      responseBuilder = error(e, requestContext);
      if(resourceDescriptor != null)
      {
        application.getStatsForResource(resourceDescriptor.getName()).
            incrementStat("delete-" + e.getStatusCode());
      }
    }

    return responseBuilder.build();
  }

  /**
   * Handles OAuth bearer token validation. This method should only be called if
   * 1) the request was not previously authenticated with HTTP Basic Auth, and
   * 2) we have a OAuthTokenHandler available.
   * 

* It will call into the OAuthTokenHandler implementation to validate the * bearer token and then do one of two things: *

    *
  • If the token is valid, it will set the output parameter 'authIDRef' * to the DN of the authorization entry and return null.
  • *
  • If the token is invalid, it will construct a Http Response with the * appropriate headers and return it.
  • *
* * @param context The incoming HTTP request context. * @param request The unmarshalled SCIM Request for passing into the * token handler. * @param authIDRef An output parameter to contain the DN of the * authorization entry. * @param tokenHandlerImpl The OAuthTokenHandler to use. * @return {@code null} if the token was successfully validated, * otherwise a Response instance containing the error * information. */ static Response validateOAuthToken(final RequestContext context, final SCIMRequest request, final AtomicReference authIDRef, final OAuthTokenHandler tokenHandlerImpl) { HttpHeaders headers = context.getHeaders(); List headerList = headers.getRequestHeader("Authorization"); if (headerList == null || headerList.isEmpty()) { //If the client lacks any authentication information, just return 401 Response.ResponseBuilder builder = Response.status(401); builder.header("WWW-Authenticate", "Bearer realm=\"SCIM\""); return builder.build(); } else if (headerList.size() > 1) { return invalidRequest("The Authorization header has too many values", context.getProduceMediaType()); } String header = headerList.get(0); String[] authorization = header.split(" "); if (authorization.length == 2 && authorization[0].equalsIgnoreCase("Bearer") && authorization[1].length() > 0) { try { OAuthToken token = tokenHandlerImpl.decodeOAuthToken(authorization[1]); if (token == null) { return invalidRequest("Could not decode the access token", context.getProduceMediaType()); } if (!tokenHandlerImpl.isTokenAuthentic(token)) { return invalidToken("The access token is not authentic", context.getProduceMediaType()); } if (!tokenHandlerImpl.isTokenForThisServer(token)) { return invalidToken( "The access token is not intended for this server", context.getProduceMediaType()); } if (tokenHandlerImpl.isTokenExpired(token)) { return invalidToken("The access token is expired", context.getProduceMediaType()); } OAuthTokenStatus status = tokenHandlerImpl.validateToken(token, request); if (status.getErrorCode().equals( OAuthTokenStatus.ErrorCode.INVALID_TOKEN)) { String errorDescription = status.getErrorDescription(); return invalidToken(errorDescription, context.getProduceMediaType()); } else if (status.getErrorCode().equals( OAuthTokenStatus.ErrorCode.INSUFFICIENT_SCOPE)) { String errorDescription = status.getErrorDescription(); String scope = status.getScope(); return insufficientScope(scope, errorDescription, context.getProduceMediaType()); } String authID = tokenHandlerImpl.getAuthzDN(token); if (authID == null) { return invalidToken( "The access token did not contain an authorization DN", context.getProduceMediaType()); } else { authIDRef.set(authID); return null; } } catch(Throwable t) { Debug.debugException(t); return invalidRequest(t.getMessage(), context.getProduceMediaType()); } } else if(authorization.length == 2 && authorization[0].equalsIgnoreCase("Basic") && authorization[1].length() > 0) { //The client tried to do Basic Authentication, and since we made it here, //it failed. Response.ResponseBuilder builder = Response.status(401); builder.header("WWW-Authenticate", "Basic realm=\"SCIM\""); SCIMException exception = new UnauthorizedException(null); setResponseEntity(builder, context.getProduceMediaType(), exception); return builder.build(); } else { return invalidRequest("The Authorization header was malformed", context.getProduceMediaType()); } } /** * Creates an invalid_request Response with the specified error description. * * @param errorDescription The description of the validation error. * @param mediaType The accept-type for SCIMRequest. * @return a Response instance. */ private static Response invalidRequest(final String errorDescription, final MediaType mediaType) { Response.ResponseBuilder builder = Response.status(400); String authHeaderValue = "Bearer realm=\"SCIM\", error=\"invalid_request\""; if (errorDescription != null && !errorDescription.isEmpty()) { authHeaderValue += ", error_description=\"" + errorDescription + "\""; } builder.header("WWW-Authenticate", authHeaderValue); SCIMException exception = new InvalidResourceException(errorDescription); setResponseEntity(builder, mediaType, exception); return builder.build(); } /** * Creates an invalid_token Response with the specified error description. * * @param errorDescription The description of the validation error. * @param mediaType The accept-type for SCIMRequest. * @return a Response instance. */ private static Response invalidToken(final String errorDescription, final MediaType mediaType) { Response.ResponseBuilder builder = Response.status(401); String authHeaderValue = "Bearer realm=\"SCIM\", error=\"invalid_token\""; if (errorDescription != null && !errorDescription.isEmpty()) { authHeaderValue += ", error_description=\"" + errorDescription + "\""; } builder.header("WWW-Authenticate", authHeaderValue); SCIMException exception = new UnauthorizedException(errorDescription); setResponseEntity(builder, mediaType, exception); return builder.build(); } /** * Creates an insufficient_scope Response with the specified error * description and scope. * * @param errorDescription The description of the validation error. * @param scope The OAuth scope required to access the target resource. * @param mediaType The accept-type for SCIMRequest. * @return a Response instance. */ private static Response insufficientScope(final String scope, final String errorDescription, final MediaType mediaType) { Response.ResponseBuilder builder = Response.status(403); String authHeaderValue = "Bearer realm=\"SCIM\", error=\"insufficient_scope\""; if (errorDescription != null && !errorDescription.isEmpty()) { authHeaderValue += ", error_description=\"" + errorDescription + "\""; } if (scope != null && !scope.isEmpty()) { authHeaderValue += ", scope=\"" + scope + "\""; } builder.header("WWW-Authenticate", authHeaderValue); SCIMException exception = new ForbiddenException(errorDescription); setResponseEntity(builder, mediaType, exception); return builder.build(); } /** * Retrieves the backend that should service the provided endpoint. * * @param endpoint The endpoint requested. * @return The backend that should service the provided endpoint. */ private SCIMBackend getBackend(final String endpoint) { if(endpoint.equals(RESOURCE_ENDPOINT_SCHEMAS)) { return resourceSchemaBackend; } return application.getBackend(); } /** * Returns a ResponseBuilder from the provided SCIMException and * RequestContext. * * @param e The SCIMException. * @param requestContext The request context. * @return The ResponseBuilder. */ private Response.ResponseBuilder error(final SCIMException e, final RequestContext requestContext) { // Build the response. Response.ResponseBuilder responseBuilder = Response.status(e.getStatusCode()); if(e instanceof NotModifiedException) { // cant use responsebuilder.tag ... it will quote the already // quoted string responseBuilder.header(HttpHeaders.ETAG, ((NotModifiedException) e).getVersion()); } else { if(e instanceof PreconditionFailedException) { // cant use responsebuilder.tag ... it will quote the already // quoted string responseBuilder.header(HttpHeaders.ETAG, ((PreconditionFailedException) e).getVersion()); } setResponseEntity(responseBuilder, requestContext.getProduceMediaType(), e); } return responseBuilder; } /** * Parse a filter string. * @param filterString The SCIM filter string. * @param resourceDescriptor ResourceDescriptor for resource being * queried. * @return SCIMFilter object. * @throws SCIMException If filter string violates SCIM syntax. */ private SCIMFilter parseFilter( final String filterString, final ResourceDescriptor resourceDescriptor) throws SCIMException { SCIMFilter filter = null; if (filterString != null && !filterString.isEmpty()) { if(resourceDescriptor.getSchema().equalsIgnoreCase( SCHEMA_URI_UBID_LDAP)) { filter = SCIMFilter.parse( filterString, resourceDescriptor.getSchema()); } else { filter = SCIMFilter.parse(filterString); } } return filter; } /** * Log the names of any query parameters provided in the request that we * won't even look at. * * @param requestContext The request context. * @param supportedParams The supported request parameters, where names * must be in lower case. */ private void logIgnoredQueryParams( final RequestContext requestContext, final Set supportedParams) { if (Debug.debugEnabled(DebugType.OTHER)) { final Set ignoredParams = new HashSet(); final MultivaluedMap map = requestContext.getUriInfo().getQueryParameters(); for (String param : map.keySet()) { if (!supportedParams.contains(param.toLowerCase())) { ignoredParams.add(param); } } if (!ignoredParams.isEmpty()) { Debug.debug( Level.WARNING, DebugType.OTHER, String.format( "These request parameters were ignored: %s. " + "Supported request parameters are: %s", ignoredParams, supportedParams)); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy