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

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

/*
 * Copyright 2012-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.ldap;

import com.unboundid.ldap.sdk.Control;
import com.unboundid.ldap.sdk.DN;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.Filter;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPSearchException;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.ldap.sdk.SearchRequest;
import com.unboundid.ldap.sdk.SearchResultEntry;
import com.unboundid.ldap.sdk.SearchScope;
import com.unboundid.scim.schema.CoreSchema;
import com.unboundid.scim.sdk.Debug;
import com.unboundid.scim.sdk.InvalidResourceException;
import com.unboundid.scim.sdk.ResourceNotFoundException;
import com.unboundid.scim.sdk.SCIMException;
import com.unboundid.util.StaticUtils;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;



/**
 * This class wraps LDAPSearchParameters and provides additional helper methods
 * for ID mapping.
 */
public class LDAPSearchResolver
{
  private final LDAPSearchParameters ldapSearchParameters;
  private final Filter filter;
  private final Set baseDNs;
  private final Set excludeBaseDNs;

  /**
   * Create a new instance of LDAPSearchResolver.
   *
   * @param ldapSearchParameters  The LDAP search parameters.
   * @param excludeBaseDNs The base DNs that should be excluded.
   *
   * @throws LDAPException  If the filter property is not a valid filter, or
   *                        the base DN is not a valid DN.
   */
  public LDAPSearchResolver(final LDAPSearchParameters ldapSearchParameters,
                            final Set excludeBaseDNs)
      throws LDAPException
  {
    this.ldapSearchParameters = ldapSearchParameters;
    this.filter = Filter.create(ldapSearchParameters.getFilter());
    Set dnSet = new HashSet();
    for (String dn : ldapSearchParameters.getBaseDN())
    {
      dnSet.add(new DN(dn));
    }
    this.baseDNs = Collections.unmodifiableSet(dnSet);
    this.excludeBaseDNs = Collections.unmodifiableSet(excludeBaseDNs);
  }



  /**
   * Retrieves the value of the baseDN property.
   *
   * @return  The value of the baseDN property.
   */
  public Set getBaseDNs()
  {
    return baseDNs;
  }



  /**
   * Retrieves the value of the filter property.
   *
   * @return  The value of the filter property.
   */
  public String getFilterString()
  {
    return ldapSearchParameters.getFilter().trim();
  }



  /**
   * Retrieves the value of the filter property as an LDAP SDK Filter.
   *
   * @return  The value of the filter property as an LDAP SDK Filter.
   */
  public Filter getFilter()
  {
    return filter;
  }



  /**
   * Determines whether the SCIM resource ID maps to the LDAP DN.
   *
   * @return  {@code true} if the SCIM resource ID maps to the LDAP DN or
   *          {@code false} if the ID maps to some other attribute.
   */
  public boolean idMapsToDn()
  {
    return ldapSearchParameters.getResourceIDMapping() == null;
  }



  /**
   * Returns the LDAP attribute that the SCIM resource ID maps to.
   *
   * @return  The LDAP attribute that the SCIM resource ID maps to, or
   *          {@code null} if the SCIM ID maps to the LDAP DN.
   */
  public String getIdAttribute()
  {
    if (ldapSearchParameters.getResourceIDMapping() == null)
    {
      return null;
    }

    return ldapSearchParameters.getResourceIDMapping().getLdapAttribute();
  }



  /**
   * Retrieve a resource ID from an LDAP entry.
   *
   * @param entry  The LDAP entry, which must contain a value for the
   *               resource ID attribute unless the resource ID maps to the
   *               LDAP DN.
   *
   * @return  The resource ID of the entry.
   *
   * @throws InvalidResourceException  If the resource ID could not be
   *                                   determined.
   */
  public String getIdFromEntry(final Entry entry)
      throws InvalidResourceException
  {
    if (idMapsToDn())
    {
      try
      {
        // We are obliged to normalize the DN because the DS does so for
        // group entries.
        return entry.getParsedDN().toNormalizedString();
      }
      catch (LDAPException e)
      {
        Debug.debugException(e);
        return entry.getDN();
      }
    }
    else
    {
      final String idAttribute = getIdAttribute();
      if (!entry.hasAttribute(idAttribute))
      {
        throw new InvalidResourceException(
            "Unable to determine a resource ID for entry '" + entry.getDN() +
            "' because it does not have a value for the '" + idAttribute +
            "' attribute");
      }
      return entry.getAttributeValue(idAttribute);
    }
  }



  /**
   * Adds the ID attribute to the provided collection.
   *
   * @param attributes  The collection of attributes to add to.
   */
  public void addIdAttribute(final Collection attributes)
  {
    if (!idMapsToDn())
    {
      attributes.add(getIdAttribute());
    }
  }



  /**
   * Returns a set of all the attributes used by the resolver, including the
   * filter attributes and the ID attribute.
   *
   * @return a set of attribute names
   */
  public Set getFilterAndIdAttributes()
  {
    Set attributes = getAllFilterAttributes(filter);

    addIdAttribute(attributes);

    return attributes;
  }



  /**
   * Returns a set of all the attributes used in the given filter. This method
   * invokes itself recursively if the given filter is a compound (AND/OR)
   * filter.
   *
   * @param filter the Filter whose attribute names to get
   * @return a set of attribute names
   */
  private static Set getAllFilterAttributes(final Filter filter)
  {
    Set attrNames = new TreeSet(String.CASE_INSENSITIVE_ORDER);
    if(filter.getFilterType() == Filter.FILTER_TYPE_AND ||
            filter.getFilterType() == Filter.FILTER_TYPE_OR)
    {
      for(Filter component : filter.getComponents())
      {
        attrNames.addAll(getAllFilterAttributes(component));
      }
      return attrNames;
    }
    else
    {
      attrNames.add(filter.getAttributeName());
      return attrNames;
    }
  }



  /**
   * Determine whether the provided DN could be a resource entry that
   * satisfies the criteria for this resolver.
   *
   * @param dn  The DN for which to make the determination.
   *
   * @return  {@code true} if the LDAP entry satisfies the criteria for this
   *          resolver.
   */
  public boolean isDnInScope(final String dn)
  {
    try
    {
      for (DN baseDN : baseDNs)
      {
        if (baseDN.isAncestorOf(dn, true))
        {
          for (DN excludeBaseDN : excludeBaseDNs)
          {
            if (excludeBaseDN.isAncestorOf(dn, true))
            {
              return false;
            }
          }
          return true;
        }
      }
      return false;
    }
    catch (LDAPException e)
    {
      Debug.debugException(e);
      return false;
    }
  }



  /**
   * Determine whether the provided LDAP entry is a resource entry that
   * satisfies the criteria for this resolver.
   *
   * @param entry  The LDAP entry for which to make the determination.
   *
   * @return  {@code true} if the LDAP entry satisfies the criteria for this
   *          resolver.
   */
  public boolean isResourceEntry(final Entry entry)
  {
    try
    {
      return isDnInScope(entry.getDN()) &&
             filter.matchesEntry(entry) &&
             (idMapsToDn() || entry.hasAttribute(getIdAttribute()));
    }
    catch (LDAPException e)
    {
      Debug.debugException(e);
      return false;
    }
  }



  /**
   * Process a resource entry before it is added.
   *
   * @param entry  The entry to be added.
   *
   * @return  The processed entry.
   *
   * @throws InvalidResourceException  If the entry is unacceptable.
   */
  public Entry preProcessAddEntry(final Entry entry)
      throws InvalidResourceException
  {
    if (!idMapsToDn() &&
        ldapSearchParameters.getResourceIDMapping().getCreatedBy() ==
        CreatedBy.SCIM_CONSUMER &&
        !entry.hasAttribute(getIdAttribute()))
    {
      throw new InvalidResourceException(
          "The entry to be added does not have a value for the '" +
          getIdAttribute() + "' LDAP attribute, which is required for the " +
          "SCIM resource ID");
    }

    return entry;
  }



  /**
   * Read the LDAP entry identified by the given resource ID.
   *
   * @param ldapInterface  The LDAP interface to use to read the entry.
   * @param resourceID     The requested SCIM resource ID.
   * @param controls       A set of search controls, which may be empty.
   * @param attributes     The requested LDAP attributes.
   *
   * @return  The LDAP entry for the given resource ID.
   *
   * @throws SCIMException  If there was an error retrieving the resource entry.
   */
  public SearchResultEntry getEntry(final LDAPRequestInterface ldapInterface,
                                    final String resourceID,
                                    final List controls,
                                    final String... attributes)
      throws SCIMException
  {
    SearchResultEntry entry = null;

    if (idMapsToDn())
    {
      if (isDnInScope(resourceID))
      {
        try
        {
          final SearchRequest searchRequest =
              new SearchRequest(resourceID, SearchScope.BASE,
                  getFilter(), attributes);
          searchRequest.setSizeLimit(1);
          searchRequest.addControls(
              controls.toArray(new Control[controls.size()]));
          entry = ldapInterface.searchForEntry(searchRequest);
        }
        catch (LDAPSearchException e)
        {
          Debug.debugException(e);
          throw ResourceMapper.toSCIMException(
              "Error searching for resource '" + resourceID + "': " +
                  StaticUtils.getExceptionMessage(e), e);
        }
      }
    }
    else
    {
      final Filter compoundFilter = Filter.createANDFilter(
               Filter.createEqualityFilter(getIdAttribute(), resourceID),
                 getFilter());

      for (DN baseDN : baseDNs)
      {
        try
        {
          final SearchRequest searchRequest =
              new SearchRequest(baseDN.toString(), SearchScope.SUB,
                      compoundFilter, attributes);
          searchRequest.setSizeLimit(1);
          searchRequest.addControls(
              controls.toArray(new Control[controls.size()]));
          entry = ldapInterface.searchForEntry(searchRequest);

          if (entry != null)
          {
            for (DN excludeBaseDN : excludeBaseDNs)
            {
              if (excludeBaseDN.isAncestorOf(entry.getParsedDN(), true))
              {
                entry = null;
                break;
              }
            }
            if(entry != null)
            {
              break;
            }
          }
        }
        catch (LDAPException e)
        {
          Debug.debugException(e);
          if(e.getResultCode() != ResultCode.INVALID_ATTRIBUTE_SYNTAX)
          {
            throw ResourceMapper.toSCIMException(
                "Error searching for resource '" + resourceID + "': " +
                    StaticUtils.getExceptionMessage(e), e);
          }
          // This is likely if the provided resource ID value violates
          // the mapped LDAP attribute's syntax. This should map to 404
          // instead of 400 since SCIM treats the resource ID as an opaque
          // value and shouldn't enforce any syntax on it.
          entry = null;
        }
      }
    }

    if (entry == null)
    {
      throw new ResourceNotFoundException(
          "Resource '" + resourceID + "' not found");
    }

    return entry;
  }



  /**
   * Determine the DN of the LDAP entry identified by the given resource ID.
   *
   * @param ldapInterface  The LDAP interface to use to read the entry.
   * @param resourceID     The requested SCIM resource ID.
   *
   * @return  The LDAP DN for the given resource ID.
   *
   * @throws SCIMException  If there was an error determining the resource DN.
   */
  public String getDnFromId(final LDAPRequestInterface ldapInterface,
                            final String resourceID)
      throws SCIMException
  {
    String dn = null;

    if (idMapsToDn())
    {
      if (isDnInScope(resourceID))
      {
        dn = resourceID;
      }
    }
    else
    {
      final Filter compoundFilter = Filter.createANDFilter(
              Filter.createEqualityFilter(getIdAttribute(), resourceID),
              getFilter());

      for (DN baseDN : baseDNs)
      {
        try
        {
          final SearchRequest searchRequest =
             new SearchRequest(baseDN.toString(), SearchScope.SUB,
                  compoundFilter, getIdAttribute());
          searchRequest.setSizeLimit(1);
          Entry entry = ldapInterface.searchForEntry(searchRequest);
          if (entry != null)
          {
            dn = entry.getDN();
            break;
          }
        }
        catch(LDAPSearchException e)
        {
          Debug.debugException(e);
          if(e.getResultCode() != ResultCode.INVALID_ATTRIBUTE_SYNTAX)
          {
            throw ResourceMapper.toSCIMException(
                "Error searching for resource '" + resourceID + "': " +
                   StaticUtils.getExceptionMessage(e), e);
          }
          // This is likely if the provided resource ID value violates
          // the mapped LDAP attribute's syntax. This should map to 404
          // instead of 400 since SCIM treats the resource ID as an opaque
          // value and shouldn't enforce any syntax on it.
        }
      }
    }

    if (dn == null)
    {
      throw new ResourceNotFoundException(
          "Resource '" + resourceID + "' not found");
    }

    return dn;
  }



  /**
   * Determine the resource ID of the resource identified by the given DN.
   *
   * @param ldapInterface  The LDAP interface to use to read the entry if
   *                       necessary.
   * @param dn             The DN of the requested resource.
   *
   * @return  The resource ID for the given DN.
   *
   * @throws SCIMException  If there was an error determining the resource ID.
   */
  public String getIdFromDn(final LDAPRequestInterface ldapInterface,
                            final String dn)
      throws SCIMException
  {
    if (idMapsToDn())
    {
      return getIdFromEntry(new Entry(dn));
    }
    else
    {
      final Entry entry;
      try
      {
        final SearchRequest searchRequest =
            new SearchRequest(dn, SearchScope.BASE,
                getFilter(), getIdAttribute());
        searchRequest.setSizeLimit(1);
        entry = ldapInterface.searchForEntry(searchRequest);
      }
      catch (LDAPSearchException e)
      {
        Debug.debugException(e);
        throw ResourceMapper.toSCIMException(
            "Error searching for resource with DN '" + dn + "': " +
                StaticUtils.getExceptionMessage(e), e);
      }
      if (entry != null)
      {
        return getIdFromEntry(entry);
      }
    }

    throw new ResourceNotFoundException(
        "Resource with DN '" + dn + "' not found");
  }



  /**
   * Retrieve an attribute mapper for the id attribute.
   *
   * @return  An attribute mapper for the id attribute, or {@code null} if the
   *          id maps to the DN.
   */
  public AttributeMapper getIdAttributeMapper()
  {
    if (idMapsToDn())
    {
      return null;
    }
    else
    {
      final AttributeTransformation attributeTransformation =
          AttributeTransformation.create(
              ldapSearchParameters.getResourceIDMapping());

      return new SimpleAttributeMapper(CoreSchema.ID_DESCRIPTOR,
                                       attributeTransformation);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy