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

com.unboundid.scim.sdk.SCIMService 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.sdk;

import com.unboundid.scim.data.GroupResource;
import com.unboundid.scim.data.BaseResource;
import com.unboundid.scim.data.ResourceFactory;
import com.unboundid.scim.data.ServiceProviderConfig;
import com.unboundid.scim.data.UserResource;
import com.unboundid.scim.schema.CoreSchema;
import com.unboundid.scim.schema.ResourceDescriptor;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import com.unboundid.scim.facade.org.apache.wink.client.RestClient;
import org.glassfish.jersey.apache.connector.ApacheClientProperties;
import org.glassfish.jersey.apache.connector.ApacheConnectorProvider;
import org.glassfish.jersey.client.ClientConfig;

import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.core.MediaType;
import java.io.IOException;
import java.net.URI;
import java.util.List;

import static com.unboundid.scim.schema.CoreSchema
                 .createCustomGroupResourceDescriptor;
import static com.unboundid.scim.schema.CoreSchema
                 .createCustomUserResourceDescriptor;


/**
 * The SCIMService class represents a client connection to a SCIM service
 * provider. It handles setting up and configuring the connection which will
 * be used by the SCIMEndpoints that are obtained form this SCIMService.
 */
public class SCIMService
{
  private final RestClient client;
  private final URI baseURL;

  private MediaType acceptType = MediaType.APPLICATION_JSON_TYPE;
  private MediaType contentType = MediaType.APPLICATION_JSON_TYPE;
  private final boolean[] overrides = new boolean[3];
  private String userAgent;
  private boolean useUrlSuffix;

  /**
   * Constructs a new SCIMService from a url and a jersey client config.
   * @param baseUrl The SCIM Service Provider URL.
  * @param clientConfig The client config object.
   */
  public SCIMService(final URI baseUrl,
    final org.glassfish.jersey.client.ClientConfig clientConfig)
  {
    this.baseURL = baseUrl;
    this.client = new RestClient(clientConfig);
  }

  /**
   * Constructs a new SCIMService.
   *
   * @param baseUrl The SCIM Service Provider URL.
   */
  public SCIMService(final URI baseUrl)
  {
    this(baseUrl, createDefaultClientConfig());
  }

  /**
   * Constructs a new SCIMService with OAuth authentication support
   * using the provided credentials.

   * @param baseUrl The SCIM Service Provider URL.
   * @param oAuthToken The OAuth token.
   */
  public SCIMService(final URI baseUrl, final OAuthToken oAuthToken) {
    this(baseUrl, createDefaultClientConfig().register(
        new ClientRequestFilter()
        {
          public void filter(final ClientRequestContext clientRequestContext)
              throws IOException
          {
            try
            {
              clientRequestContext.getHeaders().add(
                  "Authorization", oAuthToken.getFormattedValue());
            }
            catch (Exception ex)
            {
              throw new RuntimeException(
                  "Unable to add authorization handler", ex);
            }
          }
        }
    ));
  }

  /**
   * Constructs a new SCIMService with basic authentication support
   * using the provided credentials.
   *
   * @param baseUrl The SCIM Service Provider URL.
   * @param username The username.
   * @param password The password.
   */
  public SCIMService(final URI baseUrl, final String username,
                     final String password)
  {
    this(baseUrl, createDefaultClientConfig().
        property(ApacheClientProperties.CREDENTIALS_PROVIDER,
            createBasicCredentialsProvider(username, password)).
        property(ApacheClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION, true));
  }

  /**
   * Returns a SCIMEndpoint with the current settings that can be used to
   * invoke CRUD operations. Any changes to the SCIMService configuration will
   * not be reflected in the returned SCIMEndpoint.
   *
   * @param resourceDescriptor The ResourceDescriptor of the endpoint.
   * @param resourceFactory The ResourceFactory that should be used to
   *                        create SCIM resource instances.
   * @param  The type of SCIM resource instances.
   * @return The SCIMEndpoint that can be used to invoke CRUD operations.
   */
  public  SCIMEndpoint getEndpoint(
      final ResourceDescriptor resourceDescriptor,
      final ResourceFactory resourceFactory)
  {
    return new SCIMEndpoint(this, client, resourceDescriptor,
        resourceFactory);
  }


  /**
   * Returns a SCIMEndpoint for the specified endpoint.
   *
   * @param endpointPath SCIM endpoint relative path, e.g. "Users".
   * @return SCIMEndpoint that can be used to invoke CRUD operations.
   * @throws SCIMException for an invalid endpoint path
   */
  public SCIMEndpoint getEndpoint(
      final String endpointPath) throws SCIMException
  {
    return getEndpoint(endpointPath, BaseResource.BASE_RESOURCE_FACTORY);
  }


  /**
   * Returns a SCIMEndpoint for the specified endpoint.
   *
   * @param endpointPath SCIM endpoint relative path, e.g. "Users".
   * @param resourceFactory The ResourceFactory that should be used to
   *                        create SCIM resource instances.
   * @param  the type of SCIM resource instances.
   * @return SCIMEndpoint that can be used to invoke CRUD operations.
   * @throws SCIMException for an invalid endpoint path.
   */
  public  SCIMEndpoint getEndpoint(
      final String endpointPath,
      final ResourceFactory resourceFactory)
      throws SCIMException
  {
    ResourceDescriptor descriptor =
        getResourceDescriptorForEndpoint(endpointPath);
    if (descriptor == null) {
      throw new ResourceNotFoundException(
          "No schema found for endpoint " + endpointPath);
    }
    return getEndpoint(descriptor, resourceFactory);
  }



  /**
   * Returns a SCIMEndpoint for the Users endpoint defined in the core schema.
   *
   * @return The SCIMEndpoint for the Users endpoint defined in the core schema.
   */
  public SCIMEndpoint getUserEndpoint()
  {
    return new SCIMEndpoint(this, client,
        CoreSchema.USER_DESCRIPTOR, UserResource.USER_RESOURCE_FACTORY);
  }



  /**
   * Returns a SCIMEndpoint for the Users endpoint defined in the core schema
   * with a custom user resource name and users endpoint name.
   *
   * @param userResourceName   Provide a custom user resource name.
   * @param usersEndpointName  Provide a custom users endpoint name.
   * @return The SCIMEndpoint for the Users endpoint defined in the core schema.
   */
  public SCIMEndpoint getUserEndpoint(
    final String userResourceName, final String usersEndpointName)
  {
    return new SCIMEndpoint(this, client,
      createCustomUserResourceDescriptor(userResourceName, usersEndpointName),
      UserResource.USER_RESOURCE_FACTORY);
  }



  /**
   * Returns a SCIMEndpoint for the Groups endpoint defined in the core schema.
   *
   * @return The SCIMEndpoint for the Groups endpoint defined in the
   *         core schema.
   */
  public SCIMEndpoint getGroupEndpoint()
  {
    return new SCIMEndpoint(this, client,
        CoreSchema.GROUP_DESCRIPTOR, GroupResource.GROUP_RESOURCE_FACTORY);
  }



  /**
   * Returns a SCIMEndpoint for the Groups endpoint defined in the core schema
   * with a custom group resource name and groups endpoint name.
   *
   * @param groupResourceName   Provide a custom group resource name.
   * @param groupsEndpointName  Provide a custom groups endpoint name.
   * @return The SCIMEndpoint for the Groups endpoint defined in the
   *         core schema.
   */
  public SCIMEndpoint getGroupEndpoint(
    final String groupResourceName, final String groupsEndpointName)
  {
    return new SCIMEndpoint(this, client,
      createCustomGroupResourceDescriptor(groupResourceName,
      groupsEndpointName), GroupResource.GROUP_RESOURCE_FACTORY);
  }



  /**
   * Returns a SCIMEndpoint for the Schemas endpoint. This endpoint allows for
   * the retrieval of schema for all service provider supported resources.
   *
   * @return The SCIMEndpoint for the Schemas endpoint.
   */
  public SCIMEndpoint getResourceSchemaEndpoint()
  {
    return new SCIMEndpoint(this, client,
        CoreSchema.RESOURCE_SCHEMA_DESCRIPTOR,
        ResourceDescriptor.RESOURCE_DESCRIPTOR_FACTORY);
  }

  /**
   * Retrieves the ResourceDescriptor for the specified resource from the
   * SCIM service provider.
   *
   * @param resourceName The name of the resource.
   * @param schema The schema URN of the resource or null
   *        to match only based on the name of the resource.
   * @return The ResourceDescriptor for the specified resource or
   *         null if none are found.
   * @throws SCIMException If the ResourceDescriptor could not be read.
   */
  public ResourceDescriptor getResourceDescriptor(final String resourceName,
                                                  final String schema)
      throws SCIMException
  {
    final SCIMEndpoint endpoint =
        getResourceSchemaEndpoint();
    String filter = "name eq \"" + resourceName + "\"";
    if(schema != null)
    {
      filter += " and schema eq \"" + schema + "\"";
    }
    final Resources resources = endpoint.query(filter);
    if(resources.getTotalResults() == 0)
    {
      return null;
    }
    if(resources.getTotalResults() > 1)
    {
      throw new InvalidResourceException(
          "The service provider returned multiple resource descriptors " +
              "with resource name '" + resourceName);
    }

    ResourceDescriptor descriptor = resources.iterator().next();
    descriptor.setStrictMode(false);

    return descriptor;
  }

  /**
   * Retrieves the ResourceDescriptor for the specified endpoint from the
   * SCIM service provider.
   *
   * @param endpoint The name of the SCIM endpoint, e.g. "Users".
   * @return The ResourceDescriptor for the specified endpoint or
   *         null if none are found.
   * @throws SCIMException If the ResourceDescriptor could not be read.
   */
  public ResourceDescriptor getResourceDescriptorForEndpoint(
      final String endpoint)
      throws SCIMException
  {
    final SCIMEndpoint schemaEndpoint =
        getResourceSchemaEndpoint();
    String filter = "endpoint eq \"" + endpoint + "\"";

    final Resources resources =
        schemaEndpoint.query(filter);
    if(resources.getTotalResults() == 0)
    {
      return null;
    }
    if(resources.getTotalResults() > 1)
    {
      throw new InvalidResourceException(
          "The service provider returned multiple resource descriptors " +
              "for endpoint '" + endpoint);
    }

    ResourceDescriptor descriptor = resources.iterator().next();
    descriptor.setStrictMode(false);

    return descriptor;
  }

  /**
   * Retrieves the Service Provider Config from the SCIM service provider.
   *
   * @return  The Service Provider Config.
   *
   * @throws SCIMException  If the Service Provider Config could not be read.
   */
  public ServiceProviderConfig getServiceProviderConfig()
      throws SCIMException
  {
    final SCIMEndpoint endpoint =
        getEndpoint(CoreSchema.SERVICE_PROVIDER_CONFIG_SCHEMA_DESCRIPTOR,
            ServiceProviderConfig.SERVICE_PROVIDER_CONFIG_RESOURCE_FACTORY);

    // The ServiceProviderConfig is a special case where there is only a
    // single resource at the endpoint, so the id is not specified.
    return endpoint.get(null);
  }



  /**
   * Invoke a bulk request. The service provider will perform as
   * many operations as possible without regard to the number of failures.
   *
   * @param operations  The operations to be performed.
   *
   * @return  The bulk response.
   *
   * @throws SCIMException  If the request fails.
   */
  public BulkResponse processBulkRequest(
      final List operations)
      throws SCIMException
  {
    return processBulkRequest(operations, -1);
  }



  /**
   * Invoke a bulk request.
   *
   * @param operations    The operations to be performed.
   * @param failOnErrors  The number of errors that the service provider will
   *                      accept before the operation is terminated and an
   *                      error response is returned. A value of -1 indicates
   *                      the the service provider will continue to perform
   *                      as many operations as possible without regard to
   *                      failures.
   *
   * @return  The bulk response.
   *
   * @throws SCIMException  If the request fails.
   */
  public BulkResponse processBulkRequest(
      final List operations,
      final int failOnErrors)
      throws SCIMException
  {
    final BulkEndpoint request = new BulkEndpoint(this, client);

    return request.processRequest(operations, failOnErrors);
  }



  /**
   * Retrieves the SCIM Service Provider URL.
   *
   * @return The SCIM Service Provider URL.
   */
  public URI getBaseURL() {
    return baseURL;
  }

  /**
   * Retrieves the content media type that should be used when writing data to
   * the SCIM service provider.
   *
   * @return The content media type that should be used when writing data to
   * the SCIM service provider.
   */
  public MediaType getContentType() {
    return contentType;
  }

  /**
   * Sets the content media type that should be used when writing data to
   * the SCIM service provider.
   *
   * @param contentType he content media type that should be used when writing
   * data to the SCIM service provider.
   */
  public void setContentType(final MediaType contentType) {
    this.contentType = contentType;
  }

  /**
   * Retrieves the accept media type that should be used when reading data from
   * the SCIM service provider.
   *
   * @return The accept media type that should be used when reading data from
   * the SCIM service provider.
   */
  public MediaType getAcceptType() {
    return acceptType;
  }

  /**
   * Sets the accept media type that should be used when reading data from
   * the SCIM service provider.
   *
   * @param acceptType The accept media type that should be used when reading
   * data from the SCIM service provider.
   */
  public void setAcceptType(final MediaType acceptType) {
    this.acceptType = acceptType;
  }

  /**
   * Retrieves the user-agent string that will be used in the HTTP request
   * headers.
   *
   * @return The user-agent string. This may be null, in which case a default
   * user-agent will be used.
   */
  public String getUserAgent() {
    return userAgent;
  }

  /**
   * Sets the user-agent string to use in the request headers.
   *
   * @param userAgent The user-agent string that should be used.
   */
  public void setUserAgent(final String userAgent) {
    this.userAgent = userAgent;
  }

  /**
   * Whether to override DELETE operations with POST.
   *
   * @return true to override DELETE operations with POST or
   * false to use the DELETE method.
   */
  public boolean isOverrideDelete() {
    return overrides[2];
  }

  /**
   * Sets whether to override DELETE operations with POST.
   *
   * @param overrideDelete true to override DELETE operations with
   * POST or false to use the DELETE method.
   */
  public void setOverrideDelete(final boolean overrideDelete) {
    this.overrides[2] = overrideDelete;
  }

  /**
   * Whether to override PATCH operations with POST.
   *
   * @return true to override PATCH operations with POST or
   * false to use the PATCH method.
   */
  public boolean isOverridePatch() {
    return overrides[1];
  }

  /**
   * Sets whether to override PATCH operations with POST.
   *
   * @param overridePatch true to override PATCH operations with
   * POST or false to use the PATCH method.
   */
  public void setOverridePatch(final boolean overridePatch) {
    this.overrides[1] = overridePatch;
  }

  /**
   * Whether to override PUT operations with POST.
   *
   * @return true to override PUT operations with POST or
   * false to use the PUT method.
   */
  public boolean isOverridePut() {
    return overrides[0];
  }

  /**
   * Sets whether to override PUT operations with POST.
   *
   * @param overridePut true to override PUT operations with
   * POST or false to use the PUT method.
   */
  public void setOverridePut(final boolean overridePut) {
    this.overrides[0] = overridePut;
  }

  /**
   * Whether to use URL suffix to specify the desired response data format
   * (ie. .json or .xml) instead of using the HTTP Accept Header.
   *
   * @return {@code true} to use URL suffix to specify the desired response
   *         data format or {@code false} to use the HTTP Accept Header.
   */
  public boolean isUseUrlSuffix()
  {
    return useUrlSuffix;
  }

  /**
   * Sets whether to use URL suffix to specify the desired response data format
   * (ie. .json or .xml) instead of using the HTTP Accept Header.
   *
   * @param useUrlSuffix {@code true} to use URL suffix to specify the desired
   *                     response data format or {@code false} to use the HTTP
   *                     Accept Header.
   */
  public void setUseUrlSuffix(final boolean useUrlSuffix)
  {
    this.useUrlSuffix = useUrlSuffix;
  }

  /**
   * Create a new ClientConfig with the default settings.
   *
   * @return A new ClientConfig with the default settings.
   */
  private static ClientConfig createDefaultClientConfig() {
    final PoolingHttpClientConnectionManager mgr =
        new PoolingHttpClientConnectionManager();
    mgr.setMaxTotal(100);
    mgr.setDefaultMaxPerRoute(100);

    ClientConfig jerseyConfig = new ClientConfig();
    ApacheConnectorProvider connectorProvider = new ApacheConnectorProvider();
    jerseyConfig.connectorProvider(connectorProvider);
    return jerseyConfig;
  }

  /**
   * Create a new BasicCredentialsProvider with the provided credentials.
   *
   * @param username The username.
   * @param password The password.
   * @return A new BasicCredentialsProvider.
   */
  private static BasicCredentialsProvider createBasicCredentialsProvider(
      final String username, final String password)
  {
    BasicCredentialsProvider provider = new BasicCredentialsProvider();
    provider.setCredentials(
        AuthScope.ANY,
        new UsernamePasswordCredentials(username, password)
    );
    return provider;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy