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

com.unboundid.scim.ldap.LDAPBackend Maven / Gradle / Ivy

Go to download

The UnboundID SCIM-LDAP module builds on the UnboundID SCIM-SDK to provide classes that map SCIM resources to LDAP entries and vice versa. It also contains several APIs that may be used to implement custom behaviors for the mapping configuration file to extend its capabilities above and beyond those provided out of the box. Each extension type varies in the amount of control the implementation has over the mapping process and the amount of effort required for implementation.

There is a newer version: 1.8.26
Show newest version
/*
 * Copyright 2011-2012 UnboundID Corp.
 *
 * 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.ldap;

import com.unboundid.ldap.sdk.AddRequest;
import com.unboundid.ldap.sdk.Attribute;
import com.unboundid.ldap.sdk.Control;
import com.unboundid.ldap.sdk.DN;
import com.unboundid.ldap.sdk.DeleteRequest;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.Filter;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.Modification;
import com.unboundid.ldap.sdk.ModificationType;
import com.unboundid.ldap.sdk.ModifyDNRequest;
import com.unboundid.ldap.sdk.ModifyRequest;
import com.unboundid.ldap.sdk.RDN;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.ldap.sdk.SearchRequest;
import com.unboundid.ldap.sdk.SearchResult;
import com.unboundid.ldap.sdk.SearchScope;
import com.unboundid.ldap.sdk.controls.PostReadRequestControl;
import com.unboundid.ldap.sdk.controls.PostReadResponseControl;
import com.unboundid.ldap.sdk.controls.ServerSideSortRequestControl;
import com.unboundid.ldap.sdk.controls.SortKey;
import com.unboundid.ldap.sdk.controls.VirtualListViewRequestControl;
import com.unboundid.ldap.sdk.controls.VirtualListViewResponseControl;
import com.unboundid.scim.data.Meta;
import com.unboundid.scim.data.BaseResource;
import com.unboundid.scim.schema.ResourceDescriptor;
import com.unboundid.scim.sdk.Debug;
import com.unboundid.scim.sdk.InvalidResourceException;
import com.unboundid.scim.sdk.ResourceNotFoundException;
import com.unboundid.scim.sdk.Resources;
import com.unboundid.scim.sdk.SCIMBackend;
import com.unboundid.scim.sdk.SCIMException;
import com.unboundid.scim.sdk.SCIMQueryAttributes;
import com.unboundid.scim.sdk.SCIMRequest;
import com.unboundid.scim.sdk.SCIMFilter;
import com.unboundid.scim.sdk.SCIMFilterType;
import com.unboundid.scim.sdk.AttributePath;
import com.unboundid.scim.sdk.SCIMAttribute;
import com.unboundid.scim.sdk.PageParameters;
import com.unboundid.scim.sdk.ServerErrorException;
import com.unboundid.scim.sdk.SortParameters;
import com.unboundid.scim.sdk.GetResourceRequest;
import com.unboundid.scim.sdk.GetResourcesRequest;
import com.unboundid.scim.sdk.PostResourceRequest;
import com.unboundid.scim.sdk.DeleteResourceRequest;
import com.unboundid.scim.sdk.PutResourceRequest;
import com.unboundid.scim.sdk.UnsupportedOperationException;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.Validator;

import javax.ws.rs.core.UriBuilder;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;

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



/**
 * This abstract class is a base class for implementations of the SCIM server
 * backend API that use an LDAP-based resource storage repository.
 */
public abstract class LDAPBackend
    extends SCIMBackend
{
  /**
   * The default set of timestamp attributes that we need to ask for when
   * making requests to the underlying LDAP server.
   */
  private static final Set DEFAULT_LASTMOD_ATTRS;

  /**
   * The resource mappers configured for SCIM resource end-points.
   */
  private volatile Map resourceMappers;

  static
  {
    HashSet attrs = new HashSet(2);
    attrs.add("createTimestamp");
    attrs.add("modifyTimestamp");
    DEFAULT_LASTMOD_ATTRS = Collections.unmodifiableSet(attrs);
  }



  /**
   * Create a new instance of an LDAP backend.
   *
   * @param  resourceMappers  The resource mappers configured for SCIM resource
   *                          end-points.
   */
  public LDAPBackend(
      final Map resourceMappers)
  {
    this.resourceMappers = resourceMappers;
  }



  /**
   * Specifies the resource mappers configured for SCIM resource end-points.
   * @param resourceMappers  The resource mappers configured for SCIM resource
   *                         end-points.
   */
  public void setResourceMappers(
      final Map resourceMappers)
  {
    this.resourceMappers = resourceMappers;
  }



  /**
   * Retrieve an LDAP interface that may be used to interact with the LDAP
   * server.
   *
   *
   * @param userID  The authenticated user ID for the request being processed.
   *
   * @return  An LDAP interface that may be used to interact with the LDAP
   *          server.
   *
   * @throws SCIMException  If there was a problem retrieving an LDAP interface.
   */
  protected abstract LDAPRequestInterface getLDAPRequestInterface(
      final String userID)
      throws SCIMException;



  /**
   * Get the names of the create-time and modify-time attributes to request
   * when searching the directory server. Typically these will be
   * 'createTimestamp' and 'modifyTimestamp', but this can be overridden by
   * subclasses.
   *
   * @return the set of last-mod attributes to request when performing
   *         operations which return an entry from the directory server.
   */
  protected Set getLastModAttributes()
  {
    return DEFAULT_LASTMOD_ATTRS;
  }



  /**
   * Retrieve the resource mapper for the provided resource descriptor.
   *
   * @param resourceDescriptor The ResourceDescriptor for which the resource
   *                           mapper is requested.
   *
   * @return  The resource mapper for the provided resource descriptor.
   *
   * @throws  SCIMException  If there is no such resource mapper.
   */
  ResourceMapper getResourceMapper(final ResourceDescriptor resourceDescriptor)
      throws SCIMException
  {
    final ResourceMapper mapper = resourceMappers.get(resourceDescriptor);
    if (mapper == null)
    {
      throw new ServerErrorException(
          "No resource mapper found for resource '" +
          resourceDescriptor.getName() + "'");
    }

    return mapper;
  }



  @Override
  public BaseResource getResource(
      final GetResourceRequest request) throws SCIMException
  {
    final ResourceMapper mapper =
        getResourceMapper(request.getResourceDescriptor());

    final Set requestAttributeSet = new HashSet();
    requestAttributeSet.addAll(
        mapper.toLDAPAttributeTypes(request.getAttributes()));
    requestAttributeSet.addAll(getLastModAttributes());

    final String[] requestAttributes = new String[requestAttributeSet.size()];
    requestAttributeSet.toArray(requestAttributes);

    final LDAPRequestInterface ldapInterface =
        getLDAPRequestInterface(request.getAuthenticatedUserID());

    final Entry entry =
        mapper.getEntry(ldapInterface, request.getResourceID(),
                        requestAttributes);
    final BaseResource resource =
        new BaseResource(request.getResourceDescriptor());

    setIdAndMetaAttributes(mapper, resource, request, entry,
                           request.getAttributes());

    final List attributes = mapper.toSCIMAttributes(
        entry, request.getAttributes(), ldapInterface);
    for (final SCIMAttribute a : attributes)
    {
      Validator.ensureTrue(resource.getScimObject().addAttribute(a));
    }

    return resource;
  }



  @Override
  public Resources getResources(final GetResourcesRequest request)
      throws SCIMException
  {
    final ResourceMapper resourceMapper =
        getResourceMapper(request.getResourceDescriptor());
    if (resourceMapper == null || !resourceMapper.supportsQuery())
    {
      throw new UnsupportedOperationException(
          "The requested operation is not supported on resource end-point '" +
              request.getResourceDescriptor().getEndpoint() + "'");
    }

    try
    {
      final SCIMFilter scimFilter = request.getFilter();

      final Set requestAttributeSet =
          resourceMapper.toLDAPAttributeTypes(request.getAttributes());
      requestAttributeSet.addAll(getLastModAttributes());
      requestAttributeSet.add("objectClass");

      final int maxResults = getConfig().getMaxResults();
      final LDAPRequestInterface ldapInterface =
          getLDAPRequestInterface(request.getAuthenticatedUserID());
      final ResourceSearchResultListener resultListener =
          new ResourceSearchResultListener(this, request, ldapInterface,
                                           maxResults);
      SearchRequest searchRequest = null;
      if (scimFilter != null)
      {
        if (resourceMapper.idMapsToDn())
        {
          if (scimFilter.getFilterType() == SCIMFilterType.EQUALITY)
          {
            final AttributePath path = scimFilter.getFilterAttribute();
            if (path.getAttributeSchema().equalsIgnoreCase(SCHEMA_URI_CORE) &&
                path.getAttributeName().equalsIgnoreCase("id"))
            {
              final String[] requestAttributes =
                  new String[requestAttributeSet.size()];
              requestAttributeSet.toArray(requestAttributes);

              searchRequest =
                  new SearchRequest(resultListener, scimFilter.getFilterValue(),
                                    SearchScope.BASE,
                                    Filter.createPresenceFilter("objectclass"),
                                    requestAttributes);
            }
          }
        }
      }

      if (searchRequest == null)
      {
        final Filter filter;
        try
        {
          // Map the SCIM filter to an LDAP filter.
          filter = resourceMapper.toLDAPFilter(scimFilter);
        }
        catch(InvalidResourceException ire)
        {
          throw new InvalidResourceException("Invalid filter: " +
              ire.getLocalizedMessage(), ire);
        }

        if(filter == null)
        {
          // Match nothing... Just return an empty resources set.
          List emptyList = Collections.emptyList();
          return new Resources(emptyList);
        }

        // The LDAP filter results will still need to be filtered using the
        // SCIM filter, so we need to request all the filter attributes.
        addFilterAttributes(requestAttributeSet, filter);

        final String[] requestAttributes =
            new String[requestAttributeSet.size()];
        requestAttributeSet.toArray(requestAttributes);

        searchRequest =
            new SearchRequest(resultListener, resourceMapper.getSearchBaseDN(),
                SearchScope.SUB, filter, requestAttributes);
      }

      final SortParameters sortParameters = request.getSortParameters();
      if (sortParameters != null)
      {
        try
        {
          Control control = resourceMapper.toLDAPSortControl(sortParameters);
          if(control != null)
          {
            searchRequest.addControl(control);
          }
        }
        catch(InvalidResourceException ire)
        {
          throw new InvalidResourceException("Invalid sort parameters: " +
              ire.getLocalizedMessage(), ire);
        }
      }

      // Use the VLV control to perform pagination.
      final PageParameters pageParameters = request.getPageParameters();
      if (pageParameters != null)
      {
        final int count;
        if (pageParameters.getCount() <= 0)
        {
          count = getConfig().getMaxResults();
        }
        else
        {
          count = Math.min(pageParameters.getCount(),
                           getConfig().getMaxResults());
        }
        searchRequest.addControl(
            new VirtualListViewRequestControl(
                (int) pageParameters.getStartIndex(),
                0, count-1, 0, null, true));

        // VLV requires a sort control.
        if (!searchRequest.hasControl(
            ServerSideSortRequestControl.SERVER_SIDE_SORT_REQUEST_OID))
        {
          searchRequest.addControl(
              new ServerSideSortRequestControl(new SortKey("uid"))); // TODO
        }
      }

      // Invoke a search operation.
      final SearchResult searchResult = ldapInterface.search(searchRequest);

      // Prepare the response.
      final List scimObjects = resultListener.getResources();
      final Resources resources;
      final VirtualListViewResponseControl vlvResponseControl =
          getVLVResponseControl(searchResult);
      if (vlvResponseControl != null)
      {
        int startIndex = 1;
        if (pageParameters != null)
        {
          startIndex = (int)pageParameters.getStartIndex();
        }
        resources = new Resources(scimObjects,
            vlvResponseControl.getContentCount(), startIndex);
      }
      else
      {
        resources = new Resources(scimObjects);
      }

      return resources;
    }
    catch (LDAPException e)
    {
      Debug.debugException(e);
      throw ResourceMapper.toSCIMException(e);
    }
  }



  /**
   * {@inheritDoc}
   */
  @Override
  public BaseResource postResource(
      final PostResourceRequest request) throws SCIMException
  {
    final ResourceMapper mapper =
        getResourceMapper(request.getResourceDescriptor());

    final Set requestAttributeSet = new HashSet();
    requestAttributeSet.addAll(
        mapper.toLDAPAttributeTypes(request.getAttributes()));
    requestAttributeSet.addAll(getLastModAttributes());

    final String[] requestAttributes = new String[requestAttributeSet.size()];
    requestAttributeSet.toArray(requestAttributes);

    try
    {
      if (!mapper.supportsCreate())
      {
        throw new UnsupportedOperationException(
            "The '" + request.getResourceDescriptor().getName() +
            "' resource definition does not support creation of resources");
      }

      final LDAPRequestInterface ldapInterface =
          getLDAPRequestInterface(request.getAuthenticatedUserID());
      final Entry entry =
          mapper.toLDAPEntry(request.getResourceObject(), ldapInterface);

      final AddRequest addRequest = new AddRequest(entry);
      addRequest.addControl(
          new PostReadRequestControl(requestAttributes));

      final LDAPResult addResult = ldapInterface.add(addRequest);

      final PostReadResponseControl c = getPostReadResponseControl(addResult);
      Entry addedEntry = entry;
      if (c != null)
      {
        addedEntry = c.getEntry();

        // Work around issue DS-5918.
        if (addedEntry.hasAttribute("entryUUID"))
        {
          final SearchRequest r =
              new SearchRequest(entry.getDN(),
                                SearchScope.BASE,
                                Filter.createPresenceFilter("objectClass"),
                                requestAttributes);
          r.setSizeLimit(1);
          final Entry actualEntry = ldapInterface.searchForEntry(r);
          if (actualEntry != null)
          {
            addedEntry = actualEntry;
          }
        }
      }

      final BaseResource resource =
          new BaseResource(request.getResourceDescriptor());

      setIdAndMetaAttributes(mapper, resource, request, addedEntry,
                             request.getAttributes());

      final List scimAttributes = mapper.toSCIMAttributes(
          addedEntry, request.getAttributes(), ldapInterface);
      for (final SCIMAttribute a : scimAttributes)
      {
        Validator.ensureTrue(resource.getScimObject().addAttribute(a));
      }

      return resource;
    }
    catch (LDAPException e)
    {
      Debug.debugException(e);
      throw ResourceMapper.toSCIMException(e);
    }
  }



  /**
   * {@inheritDoc}
   */
  @Override
  public void deleteResource(final DeleteResourceRequest request)
      throws SCIMException
  {
    final ResourceMapper mapper =
        getResourceMapper(request.getResourceDescriptor());

    try
    {
      final LDAPRequestInterface ldapInterface =
          getLDAPRequestInterface(request.getAuthenticatedUserID());

      final Entry entry =
          mapper.getEntry(ldapInterface, request.getResourceID());
      final DeleteRequest deleteRequest = new DeleteRequest(entry.getDN());
      final LDAPResult result = ldapInterface.delete(deleteRequest);

      if (!result.getResultCode().equals(ResultCode.SUCCESS))
      {
        throw new LDAPException(result.getResultCode());
      }
    }
    catch (LDAPException e)
    {
      Debug.debugException(e);
      if (e.getResultCode().equals(ResultCode.NO_SUCH_OBJECT))
      {
        throw new ResourceNotFoundException(
            "Resource " + request.getResourceID() + " not found");
      }
      throw ResourceMapper.toSCIMException(e);
    }
  }



  /**
   * {@inheritDoc}
   */
  @Override
  public BaseResource putResource(final PutResourceRequest request)
      throws SCIMException
  {
    final ResourceMapper mapper =
        getResourceMapper(request.getResourceDescriptor());

    // Retrieve all modifiable mapped attributes to get the current state of
    // the resource.
    final Set mappedAttributeSet =
        mapper.getModifiableLDAPAttributeTypes(request.getResourceObject());
    final String[] mappedAttributes = new String[mappedAttributeSet.size()];
    mappedAttributeSet.toArray(mappedAttributes);

    final String resourceID = request.getResourceID();
    final List mods = new ArrayList();
    Entry modifiedEntry = null;
    try
    {
      final LDAPRequestInterface ldapInterface =
          getLDAPRequestInterface(request.getAuthenticatedUserID());
      final Entry currentEntry =
          mapper.getEntry(ldapInterface, resourceID, mappedAttributes);

      mods.addAll(mapper.toLDAPModifications(currentEntry,
          request.getResourceObject(), ldapInterface));

      final Set requestAttributeSet = new HashSet();
      requestAttributeSet.addAll(
          mapper.toLDAPAttributeTypes(request.getAttributes()));
      requestAttributeSet.addAll(getLastModAttributes());

      final String[] requestAttributes =
          new String[requestAttributeSet.size()];
      requestAttributeSet.toArray(requestAttributes);

      if(!mods.isEmpty())
      {
        // Look for any modifications that will affect the mapped entry's RDN
        // and split them up.
        modifiedEntry = currentEntry.duplicate();
        ListIterator iterator = mods.listIterator();
        while(iterator.hasNext())
        {
          Modification mod = iterator.next();
          if((mod.getModificationType() == ModificationType.INCREMENT ||
              mod.getModificationType() == ModificationType.REPLACE) &&
              currentEntry.getRDN().hasAttribute(mod.getAttributeName()))
          {
            iterator.remove();

            // The modification will affect the RDN so we need to first apply
            // the mods in memory and reconstruct the DN. We will set the DN to
            // null first so Entry.applyModifications wouldn't throw any
            // exceptions about affecting the RDN.
            modifiedEntry.setDN("");
            modifiedEntry =
                Entry.applyModifications(modifiedEntry, true, mod);
            modifiedEntry.setDN(mapper.constructEntryDN(modifiedEntry));

            // We might have to split the mod values into those that affects
            // the RDN and those that do not and add them after the mod DN.
            if(mod.getModificationType() == ModificationType.REPLACE &&
                mod.getValues().length > 1)
            {
              List newValues =
                  new ArrayList(mod.getValues().length - 1);
              RDN newRDN = modifiedEntry.getRDN();
              for(String value : mod.getValues())
              {
                if(!newRDN.hasAttributeValue(mod.getAttributeName(), value))
                {
                  newValues.add(value);
                }
              }

              Modification newMod =
                  new Modification(ModificationType.ADD,
                      mod.getAttributeName(),
                      newValues.toArray(new String[newValues.size()]));
              iterator.add(newMod);
            }
          }
        }

        PostReadResponseControl c = null;
        if(!modifiedEntry.getParsedDN().equals(currentEntry.getParsedDN()))
        {
          ModifyDNRequest modifyDNRequest =
              new ModifyDNRequest(currentEntry.getDN(),
                  modifiedEntry.getRDN().toString(), true);

          // If there are no other mods left, we need to include the
          // PostReadRequestControl now since we won't be performing a modify
          // operation later.
          if(mods.isEmpty())
          {
            modifyDNRequest.addControl(
                new PostReadRequestControl(requestAttributes));
          }
          final LDAPResult modifyDNResult =
              ldapInterface.modifyDN(modifyDNRequest);
          if(mods.isEmpty())
          {
            c = getPostReadResponseControl(modifyDNResult);
          }
        }

        if(!mods.isEmpty())
        {
          final ModifyRequest modifyRequest =
              new ModifyRequest(modifiedEntry.getDN(), mods);
          modifyRequest.addControl(
                new PostReadRequestControl(requestAttributes));
          final LDAPResult modifyResult = ldapInterface.modify(modifyRequest);
          c = getPostReadResponseControl(modifyResult);
        }

        if (c != null)
        {
          modifiedEntry = c.getEntry();

          // Work around issue DS-5918.
          if (modifiedEntry.hasAttribute("entryUUID"))
          {
            final SearchRequest r =
                new SearchRequest(modifiedEntry.getDN(),
                                  SearchScope.BASE,
                                  Filter.createPresenceFilter("objectClass"),
                                  requestAttributes);
            r.setSizeLimit(1);
            final Entry actualEntry = ldapInterface.searchForEntry(r);
            if (actualEntry != null)
            {
              modifiedEntry = actualEntry;
            }
          }
        }
      }
      else
      {
        // No modifications necessary (the mod set is empty).
        // Fetch the entry again, this time with the required return attributes.
        modifiedEntry =
            mapper.getEntry(ldapInterface, resourceID, requestAttributes);
      }

      final BaseResource resource =
                  new BaseResource(request.getResourceDescriptor());
      setIdAndMetaAttributes(mapper, resource, request, modifiedEntry,
                             request.getAttributes());

      final List scimAttributes = mapper.toSCIMAttributes(
        modifiedEntry, request.getAttributes(), ldapInterface);

      for (final SCIMAttribute a : scimAttributes)
      {
        Validator.ensureTrue(resource.getScimObject().addAttribute(a));
      }

      return resource;
    }
    catch (LDAPException e)
    {
      Debug.debugException(e);
      throw ResourceMapper.toSCIMException(e);
    }
  }



  /**
   * Set the id and meta attributes in a SCIM object from the provided
   * information.
   *
   * @param resourceMapper   The resource mapper for the provided resource.
   * @param resource         The SCIM object whose id and meta attributes are
   *                         to be set.
   * @param request          The SCIM request.
   * @param entry            The LDAP entry from which the attribute values are
   *                         to be derived.
   * @param queryAttributes  The request query attributes, or {@code null} if
   *                         the attributes should not be pared down.
   *
   * @throws SCIMException  If an error occurs.
   */
  public static void setIdAndMetaAttributes(
      final ResourceMapper resourceMapper,
      final BaseResource resource,
      final SCIMRequest request,
      final Entry entry,
      final SCIMQueryAttributes queryAttributes)
      throws SCIMException
  {
    final String resourceID = resourceMapper.getIdFromEntry(entry);
    resource.setId(resourceID);

    Date createDate = null;
    Attribute createTimeAttr = entry.getAttribute("createTimestamp");
    if(createTimeAttr != null && createTimeAttr.hasValue())
    {
      try
      {
        createDate =
              StaticUtils.decodeGeneralizedTime(createTimeAttr.getValue());
      }
      catch(ParseException e)
      {
        Debug.debugException(e);
      }
    }
    else
    {
      createTimeAttr = entry.getAttribute("ds-create-time");

      if (createTimeAttr != null && createTimeAttr.hasValue())
      {
        try
        {
          createDate =
              expandCompactTimestamp(createTimeAttr.getValueByteArray());
        }
        catch (Exception e)
        {
          Debug.debugException(e);
        }
      }
    }

    Date modifyDate = null;
    Attribute modifyTimeAttr = entry.getAttribute("modifyTimestamp");
    if(modifyTimeAttr != null && modifyTimeAttr.hasValue())
    {
      try
      {
        modifyDate =
              StaticUtils.decodeGeneralizedTime(modifyTimeAttr.getValue());
      }
      catch(ParseException e)
      {
        Debug.debugException(e);
      }
    }
    else
    {
      modifyTimeAttr = entry.getAttribute("ds-update-time");

      if (modifyTimeAttr != null && modifyTimeAttr.hasValue())
      {
        try
        {
          modifyDate =
              expandCompactTimestamp(modifyTimeAttr.getValueByteArray());
        }
        catch (Exception e)
        {
          Debug.debugException(e);
        }
      }
    }

    final UriBuilder uriBuilder = UriBuilder.fromUri(request.getBaseURL());
    uriBuilder.path(resource.getResourceDescriptor().getEndpoint());
    uriBuilder.path(resourceID);

    resource.setMeta(new Meta(createDate, modifyDate,
        uriBuilder.build(), null));

    if (queryAttributes != null)
    {
      final SCIMAttribute meta =
          resource.getScimObject().getAttribute(
              SCHEMA_URI_CORE, "meta");
      resource.getScimObject().setAttribute(
          queryAttributes.pareAttribute(meta));
    }
  }



  /**
   * Retrieve a SASL Authentication ID from a HTTP Basic Authentication user ID.
   * We need this because the HTTP Authentication user ID can not include the
   * ':' character.
   *
   * @param userID  The HTTP user ID for which a SASL Authentication ID is
   *                required. It may be {@code null} if the request was not
   *                authenticated.
   *
   * @return  A SASL Authentication ID.
   */
  protected String getSASLAuthenticationID(final String userID)
  {
    if (userID == null)
    {
      return "";
    }

    // If the user ID can be parsed as a DN then prefix it with "dn:", otherwise
    // prefix it with "u:".
    try
    {
      final DN dn = new DN(userID);

      return "dn:" + dn.toString();
    }
    catch (LDAPException e)
    {
      Debug.debugException(Level.FINE, e);
      return "u:" + userID;
    }
  }



  /**
   * Extracts a virtual list view response control from the provided result.
   *
   * @param  result  The result from which to retrieve the virtual list view
   *                 response control.
   *
   * @return  The virtual list view response  control contained in the provided
   *          result, or {@code null} if the result did not contain a virtual
   *          list view response control.
   *
   * @throws  LDAPException  If a problem is encountered while attempting to
   *                         decode the virtual list view response  control
   *                         contained in the provided result.
   */
  private static VirtualListViewResponseControl getVLVResponseControl(
      final SearchResult result) throws LDAPException
  {
    final Control c = result.getResponseControl(
        VirtualListViewResponseControl.VIRTUAL_LIST_VIEW_RESPONSE_OID);
    if (c == null)
    {
      return null;
    }

    if (c instanceof VirtualListViewResponseControl)
    {
      return (VirtualListViewResponseControl) c;
    }
    else
    {
      return new VirtualListViewResponseControl(c.getOID(), c.isCritical(),
          c.getValue());
    }
  }



  /**
   * Extracts a post-read response control from the provided result.
   *
   * @param  result  The result from which to retrieve the post-read response
   *                 control.
   *
   * @return  The post-read response control contained in the provided result,
   *          or {@code null} if the result did not contain a post-read response
   *          control.
   *
   * @throws  LDAPException  If a problem is encountered while attempting to
   *                         decode the post-read response control contained in
   *                         the provided result.
   */
  public static PostReadResponseControl getPostReadResponseControl(
      final LDAPResult result)
      throws LDAPException
  {
    final Control c = result.getResponseControl(
        PostReadResponseControl.POST_READ_RESPONSE_OID);
    if (c == null)
    {
      return null;
    }

    if (c instanceof PostReadResponseControl)
    {
      return (PostReadResponseControl) c;
    }
    else
    {
      return new PostReadResponseControl(c.getOID(), c.isCritical(),
          c.getValue());
    }
  }



  /**
   * Add all the attributes used in the specified filter to the provided
   * set of attributes.
   *
   * @param attributes  The set of attributes to which the filter attributes
   *                    should be added.
   *
   * @param filter      The filter whose attributes are of interest.
   */
  private static void addFilterAttributes(final Set attributes,
                                          final Filter filter)
  {
    switch (filter.getFilterType())
    {
      case Filter.FILTER_TYPE_AND:
      case Filter.FILTER_TYPE_OR:
        for (final Filter f : filter.getComponents())
        {
          addFilterAttributes(attributes, f);
        }
        break;

      case Filter.FILTER_TYPE_NOT:
        addFilterAttributes(attributes, filter.getNOTComponent());
        break;

      case Filter.FILTER_TYPE_APPROXIMATE_MATCH:
      case Filter.FILTER_TYPE_EQUALITY:
      case Filter.FILTER_TYPE_GREATER_OR_EQUAL:
      case Filter.FILTER_TYPE_LESS_OR_EQUAL:
      case Filter.FILTER_TYPE_PRESENCE:
      case Filter.FILTER_TYPE_SUBSTRING:
        attributes.add(filter.getAttributeName());
        break;
    }
  }



  /**
   * This method expands the compact representation of the 'ds-create-time' and
   * 'ds-update-time' attributes from the Directory Server. These are stored in
   * a compact 8-byte format and decoded using the
   * ExpandTimestampVirtualAttributeProvider in the core server. This code is
   * modeled after that code, so consider updating this if that class changes.
   *
   * We would prefer to use the 'createTimestamp' and 'modifyTimestamp' virtual
   * attributes so as not to have to perform this conversion, but unfortunately
   * there is a bug with retrieving them using the PostReadResponseControl,
   * which is what we use when creating a new entry via SCIM.
   *
   * @param bytes the compact representation of the timestamp to expand. This
   *        must be exactly 8 bytes long.
   * @return a Date instance constructed from long represented by the bytes
   */
  private static Date expandCompactTimestamp(final byte[] bytes)
  {
    if(bytes.length != 8)
    {
      throw new IllegalArgumentException("The compact representation of the " +
              "timestamp was not 8 bytes");
    }
    long l = 0L;
    for (int i=0; i < 8; i++)
    {
      l <<= 8;
      l |= (bytes[i] & 0xFF);
    }
    return new Date(l);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy