mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

neil_a_wilson
09.42.2007 e445569f5be5ee2cd4bb631ef882fc1093670543
Implement support for an identity mapper that can use regular expressions to
transform the provided ID string before searching for the appropriate matching
user in the server. This makes it possible, for example, to strip the realm
portion of a Kerberos V principal to obtain just the username before searching
for entries containing a uid attribute with that value.

OpenDS Issue Number: 2087
3 files added
3 files modified
1568 ■■■■■ changed files
opends/resource/config/config.ldif 13 ●●●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif 11 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/RegularExpressionIdentityMapperConfiguration.xml 180 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/RegularExpressionIdentityMapper.java 446 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/ExtensionsMessages.java 73 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/RegularExpressionIdentityMapperTestCase.java 845 ●●●●● patch | view | raw | blame | history
opends/resource/config/config.ldif
@@ -496,6 +496,17 @@
ds-cfg-identity-mapper-enabled: true
ds-cfg-match-attribute: uid
dn: cn=Regular Expression,cn=Identity Mappers,cn=config
objectClass: top
objectClass: ds-cfg-identity-mapper
objectClass: ds-cfg-regular-expression-identity-mapper
cn: Regular Expression
ds-cfg-identity-mapper-class: org.opends.server.extensions.RegularExpressionIdentityMapper
ds-cfg-identity-mapper-enabled: true
ds-cfg-match-attribute: uid
ds-cfg-match-pattern: ^([^@]+)@.+$
ds-cfg-replace-pattern: $1
dn: cn=Key Manager Providers,cn=config
objectClass: top
objectClass: ds-cfg-branch
@@ -1439,7 +1450,7 @@
cn: GSSAPI
ds-cfg-sasl-mechanism-handler-class: org.opends.server.extensions.GSSAPISASLMechanismHandler
ds-cfg-sasl-mechanism-handler-enabled: false
ds-cfg-identity-mapper-dn: cn=Exact Match,cn=Identity Mappers,cn=config
ds-cfg-identity-mapper-dn: cn=Regular Expression,cn=Identity Mappers,cn=config
ds-cfg-keytab: /etc/krb5/krb5.keytab
dn: cn=PLAIN,cn=SASL Mechanisms,cn=config
opends/resource/schema/02-config.ldif
@@ -1561,6 +1561,12 @@
  NAME 'ds-cfg-plugin-order-subordinate-modify-dn'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.469 NAME 'ds-cfg-match-pattern'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.470 NAME 'ds-cfg-replace-pattern'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.1
  NAME 'ds-cfg-access-control-handler' SUP top STRUCTURAL
  MUST ( cn $ ds-cfg-acl-handler-class $ ds-cfg-acl-handler-enabled )
@@ -2190,4 +2196,9 @@
  MUST ds-task-disconnect-connection-id
  MAY ( ds-task-disconnect-message $ ds-task-disconnect-notify-client )
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.121
  NAME 'ds-cfg-regular-expression-identity-mapper' SUP ds-cfg-identity-mapper
  STRUCTURAL MUST ( ds-cfg-match-attribute $ ds-cfg-match-pattern )
  MAY ( ds-cfg-match-base-dn $ ds-cfg-replace-pattern )
  X-ORIGIN 'OpenDS Directory Server' )
opends/src/admin/defn/org/opends/server/admin/std/RegularExpressionIdentityMapperConfiguration.xml
New file
@@ -0,0 +1,180 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
! CDDL HEADER START
!
! The contents of this file are subject to the terms of the
! Common Development and Distribution License, Version 1.0 only
! (the "License").  You may not use this file except in compliance
! with the License.
!
! You can obtain a copy of the license at
! trunk/opends/resource/legal-notices/OpenDS.LICENSE
! or https://OpenDS.dev.java.net/OpenDS.LICENSE.
! See the License for the specific language governing permissions
! and limitations under the License.
!
! When distributing Covered Code, include this CDDL HEADER in each
! file and include the License file at
! trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
! add the following below this CDDL HEADER, with the fields enclosed
! by brackets "[]" replaced with your own identifying information:
!      Portions Copyright [yyyy] [name of copyright owner]
!
! CDDL HEADER END
!
!
!      Portions Copyright 2007 Sun Microsystems, Inc.
! -->
<adm:managed-object name="regular-expression-identity-mapper"
plural-name="regular-expression-identity-mappers"
package="org.opends.server.admin.std" extends="identity-mapper"
xmlns:adm="http://www.opends.org/admin"
xmlns:ldap="http://www.opends.org/admin-ldap">
  <adm:synopsis>
    The
    <adm:user-friendly-name />
    provides a means of using a regular expression to translate the provided
    identifier when searching for the appropriate user entry.  This may be used,
    for example, if the provided identifier is expected to be an e-mail address
    or Kerberos principal, but only the username portion (the part before the
    "@" symbol) should be used in the mapping process.
    Note that a replacement will be made only if all or part of the provided ID
    string matches the given match pattern.  If no part of the provided ID
    string matches the provided pattern, then the given ID string will be used
    without any alteration.
  </adm:synopsis>
  <adm:profile name="ldap">
    <ldap:object-class>
      <ldap:oid>1.3.6.1.4.1.26027.1.2.121</ldap:oid>
      <ldap:name>ds-cfg-regular-expression-identity-mapper</ldap:name>
      <ldap:superior>ds-cfg-identity-mapper</ldap:superior>
    </ldap:object-class>
  </adm:profile>
  <adm:property-override name="mapper-class">
    <adm:default-behavior>
      <adm:defined>
        <adm:value>
          org.opends.server.extensions.RegularExpressionIdentityMapper
        </adm:value>
      </adm:defined>
    </adm:default-behavior>
  </adm:property-override>
  <adm:property name="match-attribute" mandatory="true" multi-valued="true">
    <adm:synopsis>
      Specifies the attribute to use to perform the mapping.
    </adm:synopsis>
    <adm:description>
      Specifies the name or OID of the attribute whose value should match the
      provided identifier string after it has been processed by the associated
      regular expression.  At least one value must be provided.  All values must
      refer to the name or OID of an attribute type defined in the Directory
      Server schema.  If multiple attribute type names or OIDs are provided,
      then at least one of those attributes must contain the provided ID string
      value in exactly one entry.
    </adm:description>
    <adm:syntax>
      <adm:string />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.146</ldap:oid>
        <ldap:name>ds-cfg-match-attribute</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="match-base-dn" mandatory="false" multi-valued="true">
    <adm:synopsis>
      Specifies the set of base DNs below which to search for users.
    </adm:synopsis>
    <adm:description>
      Specifies the base DN(s) that should be used when performing searches to
      map the provided ID string to a user entry.  If no values are provided,
      then the server will search below all public naming contexts.
    </adm:description>
    <adm:default-behavior>
      <adm:alias>
        <adm:synopsis>
          The server will search below all public naming contexts.
        </adm:synopsis>
      </adm:alias>
    </adm:default-behavior>
    <adm:syntax>
      <adm:dn />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.147</ldap:oid>
        <ldap:name>ds-cfg-match-base-dn</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="match-pattern" mandatory="true" multi-valued="false">
    <adm:synopsis>
      Specifies the regular expression pattern that will be used to identify
      portions of the ID string which will be replaced.
    </adm:synopsis>
    <adm:description>
      Specifies the regular expression pattern that should be used to match
      all or part of the provided ID string.  Any portion of the ID string which
      matches this pattern will be replaced in accordance with the provided
      replace pattern (or will be removed if no replace pattern is specified).
      If multiple substrings within the given ID string match this pattern, then
      all occurrences will be replaced.  If no part of the given ID string
      matches this pattern, then the ID string will not be altered.
      Exactly one match pattern value must be provided, and it must be a valid
      regular expression as described in the API documentation for the
      java.util.regex.Pattern class, including support for capturing groups.
    </adm:description>
    <adm:syntax>
      <adm:string />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.469</ldap:oid>
        <ldap:name>ds-cfg-match-pattern</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="replace-pattern" mandatory="false" multi-valued="false">
    <adm:synopsis>
      Specifies the replacement pattern that should be used for substrings in
      the ID string that match the provided regular expression pattern.  If no
      replacement pattern is provided, then any matching portions of the ID
      string will be removed.
    </adm:synopsis>
    <adm:description>
      Specifies the replacement pattern that should be used for substrings in
      the ID string that match the provided regular expression pattern.  If no
      replacement pattern is provided, then any matching portions of the ID
      string will be removed (i.e., replaced with an empty string).  The
      replacement pattern may include a string from a capturing group by using a
      dollar sign ($) followed by an integer value that indicates which
      capturing group should be used.
    </adm:description>
    <adm:default-behavior>
      <adm:alias>
        <adm:synopsis>
          The replace pattern will be the empty string.
        </adm:synopsis>
      </adm:alias>
    </adm:default-behavior>
    <adm:syntax>
      <adm:string />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.470</ldap:oid>
        <ldap:name>ds-cfg-replace-pattern</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
</adm:managed-object>
opends/src/server/org/opends/server/extensions/RegularExpressionIdentityMapper.java
New file
@@ -0,0 +1,446 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.extensions;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.SortedSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.server.RegularExpressionIdentityMapperCfg;
import org.opends.server.admin.std.server.IdentityMapperCfg;
import org.opends.server.api.IdentityMapper;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DereferencePolicy;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SearchScope;
import static org.opends.server.messages.ExtensionsMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class provides an implementation of a Directory Server identity mapper
 * that uses a regular expression to process the provided ID string, and then
 * looks for that processed value to appear in an attribute of a user's entry.
 * This mapper may be configured to look in one or more attributes using zero or
 * more search bases.  In order for the mapping to be established properly,
 * exactly one entry must have an attribute that exactly matches (according to
 * the equality matching rule associated with that attribute) the processed ID
 * value.
 */
public class RegularExpressionIdentityMapper
       extends IdentityMapper<RegularExpressionIdentityMapperCfg>
       implements ConfigurationChangeListener<
                       RegularExpressionIdentityMapperCfg>
{
  // The set of attribute types to use when performing lookups.
  private AttributeType[] attributeTypes;
  // The DN of the configuration entry for this identity mapper.
  private DN configEntryDN;
  // The set of attributes to return in search result entries.
  private LinkedHashSet<String> requestedAttributes;
  // The regular expression pattern matcher for the current configuration.
  private Pattern matchPattern;
  // The current configuration for this identity mapper.
  private RegularExpressionIdentityMapperCfg currentConfig;
  // The replacement string to use for the pattern.
  private String replacePattern;
  /**
   * Creates a new instance of this regular expression identity mapper.  All
   * initialization should be performed in the {@code initializeIdentityMapper}
   * method.
   */
  public RegularExpressionIdentityMapper()
  {
    super();
    // Don't do any initialization here.
  }
  /**
   * {@inheritDoc}
   */
  public void initializeIdentityMapper(
                   RegularExpressionIdentityMapperCfg configuration)
         throws ConfigException, InitializationException
  {
    configuration.addRegularExpressionChangeListener(this);
    currentConfig = configuration;
    configEntryDN = currentConfig.dn();
    try
    {
      matchPattern  = Pattern.compile(currentConfig.getMatchPattern());
    }
    catch (PatternSyntaxException pse)
    {
      int    msgID   = MSGID_REGEXMAP_INVALID_MATCH_PATTERN;
      String message = getMessage(msgID, currentConfig.getMatchPattern(),
                                  pse.getMessage());
      throw new ConfigException(msgID, message, pse);
    }
    replacePattern = currentConfig.getReplacePattern();
    if (replacePattern == null)
    {
      replacePattern = "";
    }
    // Get the attribute types to use for the searches.
    SortedSet<String> attrNames = currentConfig.getMatchAttribute();
    attributeTypes = new AttributeType[attrNames.size()];
    int i=0;
    for (String name : attrNames)
    {
      AttributeType type = DirectoryServer.getAttributeType(toLowerCase(name),
                                                            false);
      if (type == null)
      {
        int    msgID   = MSGID_REGEXMAP_UNKNOWN_ATTR;
        String message = getMessage(msgID, String.valueOf(configEntryDN),
                                    name);
        throw new ConfigException(msgID, message);
      }
      attributeTypes[i++] = type;
    }
    // Create the attribute list to include in search requests.  We want to
    // include all user and operational attributes.
    requestedAttributes = new LinkedHashSet<String>(2);
    requestedAttributes.add("*");
    requestedAttributes.add("+");
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void finalizeIdentityMapper()
  {
    currentConfig.removeRegularExpressionChangeListener(this);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public Entry getEntryForID(String id)
         throws DirectoryException
  {
    RegularExpressionIdentityMapperCfg config = currentConfig;
    AttributeType[] attributeTypes = this.attributeTypes;
    // Run the provided identifier string through the regular expression pattern
    // matcher and make the appropriate replacement.
    Matcher matcher = matchPattern.matcher(id);
    String processedID = matcher.replaceAll(replacePattern);
    // Construct the search filter to use to make the determination.
    SearchFilter filter;
    if (attributeTypes.length == 1)
    {
      AttributeValue value = new AttributeValue(attributeTypes[0], processedID);
      filter = SearchFilter.createEqualityFilter(attributeTypes[0], value);
    }
    else
    {
      ArrayList<SearchFilter> filterComps =
           new ArrayList<SearchFilter>(attributeTypes.length);
      for (AttributeType t : attributeTypes)
      {
        AttributeValue value = new AttributeValue(t, processedID);
        filterComps.add(SearchFilter.createEqualityFilter(t, value));
      }
      filter = SearchFilter.createORFilter(filterComps);
    }
    // Iterate through the set of search bases and process an internal search
    // to find any matching entries.  Since we'll only allow a single match,
    // then use size and time limits to constrain costly searches resulting from
    // non-unique or inefficient criteria.
    Collection<DN> baseDNs = config.getMatchBaseDN();
    if ((baseDNs == null) || baseDNs.isEmpty())
    {
      baseDNs = DirectoryServer.getPublicNamingContexts().keySet();
    }
    SearchResultEntry matchingEntry = null;
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    for (DN baseDN : baseDNs)
    {
      InternalSearchOperation internalSearch =
           conn.processSearch(baseDN, SearchScope.WHOLE_SUBTREE,
                              DereferencePolicy.NEVER_DEREF_ALIASES, 1, 10,
                              false, filter, requestedAttributes);
      switch (internalSearch.getResultCode())
      {
        case SUCCESS:
          // This is fine.  No action needed.
          break;
        case NO_SUCH_OBJECT:
          // The search base doesn't exist.  Not an ideal situation, but we'll
          // ignore it.
          break;
        case SIZE_LIMIT_EXCEEDED:
          // Multiple entries matched the filter.  This is not acceptable.
          int    msgID   = MSGID_REGEXMAP_MULTIPLE_MATCHING_ENTRIES;
          String message = getMessage(msgID, String.valueOf(processedID));
          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message,
                                       msgID);
        case TIME_LIMIT_EXCEEDED:
        case ADMIN_LIMIT_EXCEEDED:
          // The search criteria was too inefficient.
          msgID   = MSGID_REGEXMAP_INEFFICIENT_SEARCH;
          message = getMessage(msgID, String.valueOf(processedID),
                         String.valueOf(internalSearch.getErrorMessage()));
          throw new DirectoryException(internalSearch.getResultCode(), message,
                                       msgID);
        default:
          // Just pass on the failure that was returned for this search.
          msgID   = MSGID_REGEXMAP_SEARCH_FAILED;
          message = getMessage(msgID, String.valueOf(processedID),
                         String.valueOf(internalSearch.getErrorMessage()));
          throw new DirectoryException(internalSearch.getResultCode(), message,
                                       msgID);
      }
      LinkedList<SearchResultEntry> searchEntries =
           internalSearch.getSearchEntries();
      if ((searchEntries != null) && (! searchEntries.isEmpty()))
      {
        if (matchingEntry == null)
        {
          Iterator<SearchResultEntry> iterator = searchEntries.iterator();
          matchingEntry = iterator.next();
          if (iterator.hasNext())
          {
            int    msgID   = MSGID_REGEXMAP_MULTIPLE_MATCHING_ENTRIES;
            String message = getMessage(msgID, String.valueOf(processedID));
            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
                                         message, msgID);
          }
        }
        else
        {
          int    msgID   = MSGID_REGEXMAP_MULTIPLE_MATCHING_ENTRIES;
          String message = getMessage(msgID, String.valueOf(processedID));
          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message,
                                       msgID);
        }
      }
    }
    if (matchingEntry == null)
    {
      return null;
    }
    else
    {
      return matchingEntry;
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean isConfigurationAcceptable(IdentityMapperCfg configuration,
                                           List<String> unacceptableReasons)
  {
    RegularExpressionIdentityMapperCfg config =
         (RegularExpressionIdentityMapperCfg) configuration;
    return isConfigurationChangeAcceptable(config, unacceptableReasons);
  }
  /**
   * {@inheritDoc}
   */
  public boolean isConfigurationChangeAcceptable(
                      RegularExpressionIdentityMapperCfg configuration,
                      List<String> unacceptableReasons)
  {
    boolean configAcceptable = true;
    // Make sure that we can parse the match pattern.
    try
    {
      Pattern.compile(configuration.getMatchPattern());
    }
    catch (PatternSyntaxException pse)
    {
      int    msgID   = MSGID_REGEXMAP_INVALID_MATCH_PATTERN;
      String message = getMessage(msgID, configuration.getMatchPattern(),
                                  pse.getMessage());
      unacceptableReasons.add(message);
      configAcceptable = false;
    }
    // Make sure that the set of attribute types is acceptable.
    SortedSet<String> attributeNames = configuration.getMatchAttribute();
    for (String name : attributeNames)
    {
      AttributeType t = DirectoryServer.getAttributeType(toLowerCase(name),
                                                         false);
      if (t == null)
      {
        int msgID = MSGID_REGEXMAP_UNKNOWN_ATTR;
        unacceptableReasons.add(getMessage(msgID, configuration.dn(), name));
        configAcceptable = false;
      }
    }
    return configAcceptable;
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationChange(
              RegularExpressionIdentityMapperCfg configuration)
  {
    ResultCode        resultCode          = ResultCode.SUCCESS;
    boolean           adminActionRequired = false;
    ArrayList<String> messages            = new ArrayList<String>();
    Pattern newMatchPattern = null;
    try
    {
      newMatchPattern = Pattern.compile(configuration.getMatchPattern());
    }
    catch (PatternSyntaxException pse)
    {
      int    msgID   = MSGID_REGEXMAP_INVALID_MATCH_PATTERN;
      String message = getMessage(msgID, configuration.getMatchPattern(),
                                  pse.getMessage());
      messages.add(message);
      resultCode = ResultCode.CONSTRAINT_VIOLATION;
    }
    String newReplacePattern = configuration.getReplacePattern();
    if (newReplacePattern == null)
    {
      newReplacePattern = "";
    }
    // Get the attribute types to use for the searches.
    SortedSet<String> attrNames = configuration.getMatchAttribute();
    AttributeType[] newAttributeTypes = new AttributeType[attrNames.size()];
    int i=0;
    for (String name : attrNames)
    {
      AttributeType type = DirectoryServer.getAttributeType(toLowerCase(name),
                                                            false);
      if (type == null)
      {
        if (resultCode == ResultCode.SUCCESS)
        {
          resultCode = ResultCode.NO_SUCH_ATTRIBUTE;
        }
        int msgID = MSGID_REGEXMAP_UNKNOWN_ATTR;
        messages.add(getMessage(msgID, String.valueOf(configEntryDN), name));
      }
      newAttributeTypes[i++] = type;
    }
    if (resultCode == ResultCode.SUCCESS)
    {
      attributeTypes = newAttributeTypes;
      currentConfig  = configuration;
      matchPattern   = newMatchPattern;
      replacePattern = newReplacePattern;
    }
   return new ConfigChangeResult(resultCode, adminActionRequired, messages);
  }
}
opends/src/server/org/opends/server/messages/ExtensionsMessages.java
@@ -5533,6 +5533,58 @@
  /**
   * The message ID for the message that will be used if the provided match
   * pattern cannot be parsed as a valid regular expression.  This takes two
   * arguments, which are the provided match pattern and a message explaining
   * the problem that occurred.
   */
  public static final int MSGID_REGEXMAP_INVALID_MATCH_PATTERN =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 533;
  /**
   * The message ID for the message that will be used if any of the match
   * attributes is not defined in the server schema.  This takes two arguments,
   * which are the DN of the configuration entry and the value of the provided
   * attribute.
   */
  public static final int MSGID_REGEXMAP_UNKNOWN_ATTR =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 534;
  /**
   * The message ID for the message that will be used if the search to map a
   * user returned multiple entries.  This takes a single argument, which is the
   * provided ID string.
   */
  public static final int MSGID_REGEXMAP_MULTIPLE_MATCHING_ENTRIES =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 535;
  /**
   * The message ID for the message that will be used if the search to map a
   * user could not be processed efficiently.  This two arguments, which are the
   * provided ID string and the error message from the internal search.
   */
  public static final int MSGID_REGEXMAP_INEFFICIENT_SEARCH =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 536;
  /**
   * The message ID for the message that will be used if the search to map a
   * user could failed for some reason.  This two arguments, which are the
   * provided ID string and the error message from the internal search.
   */
  public static final int MSGID_REGEXMAP_SEARCH_FAILED =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 537;
  /**
   * Associates a set of generic messages with the message IDs defined in this
   * class.
   */
@@ -7951,6 +8003,27 @@
                    "An error occurred when trying to send an e-mail message " +
                    "for administrative alert with type %s and message %s:  " +
                    "%s");
    registerMessage(MSGID_REGEXMAP_INVALID_MATCH_PATTERN,
                    "The provided match pattern \"%s\" could not be parsed " +
                    "as a regular expression:  %s");
    registerMessage(MSGID_REGEXMAP_UNKNOWN_ATTR,
                    "Configuration entry %s contains value %s for attribute " +
                    ATTR_MATCH_ATTRIBUTE + " but that is not a valid name or " +
                    "OID for any attribute type defined in the Directory " +
                    "Server schema");
    registerMessage(MSGID_REGEXMAP_MULTIPLE_MATCHING_ENTRIES,
                    "The processed ID string %s mapped to multiple users");
    registerMessage(MSGID_REGEXMAP_INEFFICIENT_SEARCH,
                    "The internal search based on processed ID string %s "+
                    "could not be processed efficiently:  %s.  Check the " +
                    "server configuration to ensure that all associated " +
                    "backends are properly configured for these types of " +
                    "searches");
    registerMessage(MSGID_REGEXMAP_SEARCH_FAILED,
                    "An internal failure occurred while attempting to " +
                    "resolve processed ID string %s to a user entry:  %s");
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/RegularExpressionIdentityMapperTestCase.java
New file
@@ -0,0 +1,845 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.extensions;
import java.util.LinkedList;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.opends.server.TestCaseUtils;
import org.opends.server.admin.server.AdminTestCaseUtils;
import org.opends.server.admin.std.meta.
            RegularExpressionIdentityMapperCfgDefn;
import org.opends.server.admin.std.server.RegularExpressionIdentityMapperCfg;
import org.opends.server.api.IdentityMapper;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import static org.testng.Assert.*;
/**
 * A set of test cases for the regular expression identity mapper.
 */
public class RegularExpressionIdentityMapperTestCase
       extends ExtensionsTestCase
{
  /**
   * Ensures that the Directory Server is running.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @BeforeClass()
  public void startServer()
         throws Exception
  {
    TestCaseUtils.startServer();
  }
  /**
   * Tests to ensure that the default regular expression identity mapper is
   * configured and enabled within the Directory Server.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testMapperEnabled()
         throws Exception
  {
    DN mapperDN =
         DN.decode("cn=Regular Expression,cn=Identity Mappers,cn=config");
    IdentityMapper mapper = DirectoryServer.getIdentityMapper(mapperDN);
    assertNotNull(mapper);
    assertTrue(mapper instanceof RegularExpressionIdentityMapper);
  }
  /**
   * Tests an invalid configuration due to a bad match pattern.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ConfigException.class,
                               InitializationException.class })
  public void testConfigWithBadMatchPattern()
         throws Exception
  {
    // Create the identity mapper with an appropriate configuration for this
    // test.
    Entry mapperEntry = TestCaseUtils.makeEntry(
         "dn: cn=Regular Expression,cn=Identity Mappers,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-identity-mapper",
         "objectClass: ds-cfg-regular-expression-identity-mapper",
         "cn: Regular Expression",
         "ds-cfg-identity-mapper-class: " +
              "org.opends.server.extensions.RegularExpressionIdentityMapper",
         "ds-cfg-identity-mapper-enabled: true",
         "ds-cfg-match-attribute: uid",
         "ds-cfg-match-pattern: :-(",
         "ds-cfg-replace-pattern: $1");
    RegularExpressionIdentityMapperCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              RegularExpressionIdentityMapperCfgDefn.getInstance(),
              mapperEntry);
    RegularExpressionIdentityMapper mapper =
         new RegularExpressionIdentityMapper();
    assertFalse(mapper.isConfigurationAcceptable(configuration,
                                                 new LinkedList<String>()));
    mapper.initializeIdentityMapper(configuration);
  }
  /**
   * Tests an invalid configuration due to an unknown attribute type.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ConfigException.class,
                               InitializationException.class })
  public void testConfigWithUnknownAttributeType()
         throws Exception
  {
    // Create the identity mapper with an appropriate configuration for this
    // test.
    Entry mapperEntry = TestCaseUtils.makeEntry(
         "dn: cn=Regular Expression,cn=Identity Mappers,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-identity-mapper",
         "objectClass: ds-cfg-regular-expression-identity-mapper",
         "cn: Regular Expression",
         "ds-cfg-identity-mapper-class: " +
              "org.opends.server.extensions.RegularExpressionIdentityMapper",
         "ds-cfg-identity-mapper-enabled: true",
         "ds-cfg-match-attribute: unknown",
         "ds-cfg-match-pattern: ^([^@]+)@.+$",
         "ds-cfg-replace-pattern: $1");
    RegularExpressionIdentityMapperCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              RegularExpressionIdentityMapperCfgDefn.getInstance(),
              mapperEntry);
    RegularExpressionIdentityMapper mapper =
         new RegularExpressionIdentityMapper();
    assertFalse(mapper.isConfigurationAcceptable(configuration,
                                                 new LinkedList<String>()));
    mapper.initializeIdentityMapper(configuration);
  }
  /**
   * Tests the {@code getEntryForID} method with a simple match with only one
   * entry, a single replacement in the regular expression, and with no search
   * base DN defined.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testSimpleMatchSingleReplacementWithoutBaseDN()
         throws Exception
  {
    // Create the identity mapper with an appropriate configuration for this
    // test.
    Entry mapperEntry = TestCaseUtils.makeEntry(
         "dn: cn=Regular Expression,cn=Identity Mappers,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-identity-mapper",
         "objectClass: ds-cfg-regular-expression-identity-mapper",
         "cn: Regular Expression",
         "ds-cfg-identity-mapper-class: " +
              "org.opends.server.extensions.RegularExpressionIdentityMapper",
         "ds-cfg-identity-mapper-enabled: true",
         "ds-cfg-match-attribute: uid",
         "ds-cfg-match-pattern: ^([^@]+)@.+$",
         "ds-cfg-replace-pattern: $1");
    RegularExpressionIdentityMapperCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              RegularExpressionIdentityMapperCfgDefn.getInstance(),
              mapperEntry);
    RegularExpressionIdentityMapper mapper =
         new RegularExpressionIdentityMapper();
    assertTrue(mapper.isConfigurationAcceptable(configuration,
                                                new LinkedList<String>()));
    mapper.initializeIdentityMapper(configuration);
    // Create a user entry and add it to the directory.
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntry(
         "dn: uid=test,o=test",
         "objectClass: top",
         "objectClass: person",
         "objectClass: organizationalPerson",
         "objectClass: inetOrgPerson",
         "uid: test",
         "givenName: Test",
         "sn: Test",
         "cn: Test",
         "userPassword: password");
    // Ensure that the identity mapper is able to establish the mapping
    // successfully.
    Entry mappedEntry = mapper.getEntryForID("test@example.com");
    assertNotNull(mappedEntry);
    assertEquals(mappedEntry.getDN(), DN.decode("uid=test,o=test"));
    mapper.finalizeIdentityMapper();
  }
  /**
   * Tests the {@code getEntryForID} method with a simple match with only one
   * entry, a single replacement in the regular expression, and with no search
   * base DN defined.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testSimpleMatchSingleReplacementMultipleAttributes()
         throws Exception
  {
    // Create the identity mapper with an appropriate configuration for this
    // test.
    Entry mapperEntry = TestCaseUtils.makeEntry(
         "dn: cn=Regular Expression,cn=Identity Mappers,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-identity-mapper",
         "objectClass: ds-cfg-regular-expression-identity-mapper",
         "cn: Regular Expression",
         "ds-cfg-identity-mapper-class: " +
              "org.opends.server.extensions.RegularExpressionIdentityMapper",
         "ds-cfg-identity-mapper-enabled: true",
         "ds-cfg-match-attribute: cn",
         "ds-cfg-match-attribute: uid",
         "ds-cfg-match-pattern: ^([^@]+)@.+$",
         "ds-cfg-replace-pattern: $1");
    RegularExpressionIdentityMapperCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              RegularExpressionIdentityMapperCfgDefn.getInstance(),
              mapperEntry);
    RegularExpressionIdentityMapper mapper =
         new RegularExpressionIdentityMapper();
    assertTrue(mapper.isConfigurationAcceptable(configuration,
                                                new LinkedList<String>()));
    mapper.initializeIdentityMapper(configuration);
    // Create a user entry and add it to the directory.
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntry(
         "dn: uid=test,o=test",
         "objectClass: top",
         "objectClass: person",
         "objectClass: organizationalPerson",
         "objectClass: inetOrgPerson",
         "uid: test",
         "givenName: Test",
         "sn: Test",
         "cn: Test",
         "userPassword: password");
    // Ensure that the identity mapper is able to establish the mapping
    // successfully.
    Entry mappedEntry = mapper.getEntryForID("test@example.com");
    assertNotNull(mappedEntry);
    assertEquals(mappedEntry.getDN(), DN.decode("uid=test,o=test"));
    mapper.finalizeIdentityMapper();
  }
  /**
   * Tests the {@code getEntryForID} method with a simple match with only one
   * entry, a single replacement in the regular expression, and with a search
   * base DN defined within the scope of the user entry.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testSimpleMatchSingleReplacementWithBaseDNInScope()
         throws Exception
  {
    // Create the identity mapper with an appropriate configuration for this
    // test.
    Entry mapperEntry = TestCaseUtils.makeEntry(
         "dn: cn=Regular Expression,cn=Identity Mappers,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-identity-mapper",
         "objectClass: ds-cfg-regular-expression-identity-mapper",
         "cn: Regular Expression",
         "ds-cfg-identity-mapper-class: " +
              "org.opends.server.extensions.RegularExpressionIdentityMapper",
         "ds-cfg-identity-mapper-enabled: true",
         "ds-cfg-match-attribute: uid",
         "ds-cfg-match-base-dn: o=test",
         "ds-cfg-match-pattern: ^([^@]+)@.+$",
         "ds-cfg-replace-pattern: $1");
    RegularExpressionIdentityMapperCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              RegularExpressionIdentityMapperCfgDefn.getInstance(),
              mapperEntry);
    RegularExpressionIdentityMapper mapper =
         new RegularExpressionIdentityMapper();
    assertTrue(mapper.isConfigurationAcceptable(configuration,
                                                new LinkedList<String>()));
    mapper.initializeIdentityMapper(configuration);
    // Create a user entry and add it to the directory.
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntry(
         "dn: uid=test,o=test",
         "objectClass: top",
         "objectClass: person",
         "objectClass: organizationalPerson",
         "objectClass: inetOrgPerson",
         "uid: test",
         "givenName: Test",
         "sn: Test",
         "cn: Test",
         "userPassword: password");
    // Ensure that the identity mapper is able to establish the mapping
    // successfully.
    Entry mappedEntry = mapper.getEntryForID("test@example.com");
    assertNotNull(mappedEntry);
    assertEquals(mappedEntry.getDN(), DN.decode("uid=test,o=test"));
    mapper.finalizeIdentityMapper();
  }
  /**
   * Tests the {@code getEntryForID} method with a simple match with only one
   * entry, a single replacement in the regular expression, and with a search
   * base DN defined outside the scope of the user entry.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testSimpleMatchSingleReplacementWithBaseDNOutOfScope()
         throws Exception
  {
    // Create the identity mapper with an appropriate configuration for this
    // test.
    Entry mapperEntry = TestCaseUtils.makeEntry(
         "dn: cn=Regular Expression,cn=Identity Mappers,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-identity-mapper",
         "objectClass: ds-cfg-regular-expression-identity-mapper",
         "cn: Regular Expression",
         "ds-cfg-identity-mapper-class: " +
              "org.opends.server.extensions.RegularExpressionIdentityMapper",
         "ds-cfg-identity-mapper-enabled: true",
         "ds-cfg-match-attribute: uid",
         "ds-cfg-match-base-dn: dc=example,dc=com",
         "ds-cfg-match-pattern: ^([^@]+)@.+$",
         "ds-cfg-replace-pattern: $1");
    RegularExpressionIdentityMapperCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              RegularExpressionIdentityMapperCfgDefn.getInstance(),
              mapperEntry);
    RegularExpressionIdentityMapper mapper =
         new RegularExpressionIdentityMapper();
    assertTrue(mapper.isConfigurationAcceptable(configuration,
                                                new LinkedList<String>()));
    mapper.initializeIdentityMapper(configuration);
    // Create a user entry and add it to the directory.
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntry(
         "dn: uid=test,o=test",
         "objectClass: top",
         "objectClass: person",
         "objectClass: organizationalPerson",
         "objectClass: inetOrgPerson",
         "uid: test",
         "givenName: Test",
         "sn: Test",
         "cn: Test",
         "userPassword: password");
    // Ensure that the identity mapper is not able to establish the mapping.
    Entry mappedEntry = mapper.getEntryForID("test@example.com");
    assertNull(mappedEntry);
    mapper.finalizeIdentityMapper();
  }
  /**
   * Tests the {@code getEntryForID} method with a simple match with only one
   * entry, a single replacement in the regular expression, and with multiple
   * base DNs, one of which is in the scope of the entry.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testSimpleMatchSingleReplacementWithMultipleBaseDNs()
         throws Exception
  {
    // Create the identity mapper with an appropriate configuration for this
    // test.
    Entry mapperEntry = TestCaseUtils.makeEntry(
         "dn: cn=Regular Expression,cn=Identity Mappers,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-identity-mapper",
         "objectClass: ds-cfg-regular-expression-identity-mapper",
         "cn: Regular Expression",
         "ds-cfg-identity-mapper-class: " +
              "org.opends.server.extensions.RegularExpressionIdentityMapper",
         "ds-cfg-identity-mapper-enabled: true",
         "ds-cfg-match-attribute: uid",
         "ds-cfg-match-base-dn: dc=example,dc=com",
         "ds-cfg-match-base-dn: o=nonexistent",
         "ds-cfg-match-base-dn: o=test",
         "ds-cfg-match-pattern: ^([^@]+)@.+$",
         "ds-cfg-replace-pattern: $1");
    RegularExpressionIdentityMapperCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              RegularExpressionIdentityMapperCfgDefn.getInstance(),
              mapperEntry);
    RegularExpressionIdentityMapper mapper =
         new RegularExpressionIdentityMapper();
    assertTrue(mapper.isConfigurationAcceptable(configuration,
                                                new LinkedList<String>()));
    mapper.initializeIdentityMapper(configuration);
    // Create a user entry and add it to the directory.
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntry(
         "dn: uid=test,o=test",
         "objectClass: top",
         "objectClass: person",
         "objectClass: organizationalPerson",
         "objectClass: inetOrgPerson",
         "uid: test",
         "givenName: Test",
         "sn: Test",
         "cn: Test",
         "userPassword: password");
    // Ensure that the identity mapper is able to establish the mapping
    // successfully.
    Entry mappedEntry = mapper.getEntryForID("test@example.com");
    assertNotNull(mappedEntry);
    assertEquals(mappedEntry.getDN(), DN.decode("uid=test,o=test"));
    mapper.finalizeIdentityMapper();
  }
  /**
   * Tests the {@code getEntryForID} method for the case in which the match
   * pattern doesn't match the ID string and therefore the ID string is left
   * unchanged.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testMatchPatternDoesntMatch()
         throws Exception
  {
    // Create the identity mapper with an appropriate configuration for this
    // test.
    Entry mapperEntry = TestCaseUtils.makeEntry(
         "dn: cn=Regular Expression,cn=Identity Mappers,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-identity-mapper",
         "objectClass: ds-cfg-regular-expression-identity-mapper",
         "cn: Regular Expression",
         "ds-cfg-identity-mapper-class: " +
              "org.opends.server.extensions.RegularExpressionIdentityMapper",
         "ds-cfg-identity-mapper-enabled: true",
         "ds-cfg-match-attribute: uid",
         "ds-cfg-match-pattern: ^([^@]+)@.+$",
         "ds-cfg-replace-pattern: $1");
    RegularExpressionIdentityMapperCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              RegularExpressionIdentityMapperCfgDefn.getInstance(),
              mapperEntry);
    RegularExpressionIdentityMapper mapper =
         new RegularExpressionIdentityMapper();
    assertTrue(mapper.isConfigurationAcceptable(configuration,
                                                new LinkedList<String>()));
    mapper.initializeIdentityMapper(configuration);
    // Create a user entry and add it to the directory.
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntry(
         "dn: uid=test,o=test",
         "objectClass: top",
         "objectClass: person",
         "objectClass: organizationalPerson",
         "objectClass: inetOrgPerson",
         "uid: test",
         "givenName: Test",
         "sn: Test",
         "cn: Test",
         "userPassword: password");
    // Ensure that the identity mapper is able to establish the mapping
    // successfully.
    Entry mappedEntry = mapper.getEntryForID("test");
    assertNotNull(mappedEntry);
    assertEquals(mappedEntry.getDN(), DN.decode("uid=test,o=test"));
    mapper.finalizeIdentityMapper();
  }
  /**
   * Tests the {@code getEntryForID} method with a simple match in which no
   * replacement pattern is provided.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testSimpleMatchNoReplacePattern()
         throws Exception
  {
    // Create the identity mapper with an appropriate configuration for this
    // test.
    Entry mapperEntry = TestCaseUtils.makeEntry(
         "dn: cn=Regular Expression,cn=Identity Mappers,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-identity-mapper",
         "objectClass: ds-cfg-regular-expression-identity-mapper",
         "cn: Regular Expression",
         "ds-cfg-identity-mapper-class: " +
              "org.opends.server.extensions.RegularExpressionIdentityMapper",
         "ds-cfg-identity-mapper-enabled: true",
         "ds-cfg-match-attribute: uid",
         "ds-cfg-match-pattern: @.+$");
    RegularExpressionIdentityMapperCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              RegularExpressionIdentityMapperCfgDefn.getInstance(),
              mapperEntry);
    RegularExpressionIdentityMapper mapper =
         new RegularExpressionIdentityMapper();
    assertTrue(mapper.isConfigurationAcceptable(configuration,
                                                new LinkedList<String>()));
    mapper.initializeIdentityMapper(configuration);
    // Create a user entry and add it to the directory.
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntry(
         "dn: uid=test,o=test",
         "objectClass: top",
         "objectClass: person",
         "objectClass: organizationalPerson",
         "objectClass: inetOrgPerson",
         "uid: test",
         "givenName: Test",
         "sn: Test",
         "cn: Test",
         "userPassword: password");
    // Ensure that the identity mapper is able to establish the mapping
    // successfully.
    Entry mappedEntry = mapper.getEntryForID("test@example.com");
    assertNotNull(mappedEntry);
    assertEquals(mappedEntry.getDN(), DN.decode("uid=test,o=test"));
    mapper.finalizeIdentityMapper();
  }
  /**
   * Tests the {@code getEntryForID} method with a simple match in which the
   * replace pattern expands the string rather than shortens it.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testSimpleMatchReplacePatternExpandsString()
         throws Exception
  {
    // Create the identity mapper with an appropriate configuration for this
    // test.
    Entry mapperEntry = TestCaseUtils.makeEntry(
         "dn: cn=Regular Expression,cn=Identity Mappers,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-identity-mapper",
         "objectClass: ds-cfg-regular-expression-identity-mapper",
         "cn: Regular Expression",
         "ds-cfg-identity-mapper-class: " +
              "org.opends.server.extensions.RegularExpressionIdentityMapper",
         "ds-cfg-identity-mapper-enabled: true",
         "ds-cfg-match-attribute: uid",
         "ds-cfg-match-pattern: ^(.*)$",
         "ds-cfg-replace-pattern: $1@example.com");
    RegularExpressionIdentityMapperCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              RegularExpressionIdentityMapperCfgDefn.getInstance(),
              mapperEntry);
    RegularExpressionIdentityMapper mapper =
         new RegularExpressionIdentityMapper();
    assertTrue(mapper.isConfigurationAcceptable(configuration,
                                                new LinkedList<String>()));
    mapper.initializeIdentityMapper(configuration);
    // Create a user entry and add it to the directory.
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntry(
         "dn: uid=test@example.com,o=test",
         "objectClass: top",
         "objectClass: person",
         "objectClass: organizationalPerson",
         "objectClass: inetOrgPerson",
         "uid: test@example.com",
         "givenName: Test",
         "sn: Test",
         "cn: Test",
         "userPassword: password");
    // Ensure that the identity mapper is able to establish the mapping
    // successfully.
    Entry mappedEntry = mapper.getEntryForID("test");
    assertNotNull(mappedEntry);
    assertEquals(mappedEntry.getDN(), DN.decode("uid=test@example.com,o=test"));
    mapper.finalizeIdentityMapper();
  }
  /**
   * Tests the {@code getEntryForID} method for a case in which multiple
   * matching entries are identified below a single base DN.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { DirectoryException.class })
  public void testMultipleMatchingEntriesBelowSingleBase()
         throws Exception
  {
    // Create the identity mapper with an appropriate configuration for this
    // test.
    Entry mapperEntry = TestCaseUtils.makeEntry(
         "dn: cn=Regular Expression,cn=Identity Mappers,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-identity-mapper",
         "objectClass: ds-cfg-regular-expression-identity-mapper",
         "cn: Regular Expression",
         "ds-cfg-identity-mapper-class: " +
              "org.opends.server.extensions.RegularExpressionIdentityMapper",
         "ds-cfg-identity-mapper-enabled: true",
         "ds-cfg-match-attribute: uid",
         "ds-cfg-match-attribute: sn",
         "ds-cfg-match-pattern: ^([^@]+)@.+$",
         "ds-cfg-replace-pattern: $1");
    RegularExpressionIdentityMapperCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              RegularExpressionIdentityMapperCfgDefn.getInstance(),
              mapperEntry);
    RegularExpressionIdentityMapper mapper =
         new RegularExpressionIdentityMapper();
    assertTrue(mapper.isConfigurationAcceptable(configuration,
                                                new LinkedList<String>()));
    mapper.initializeIdentityMapper(configuration);
    // Create a user entry and add it to the directory.
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntries(
         "dn: uid=test,o=test",
         "objectClass: top",
         "objectClass: person",
         "objectClass: organizationalPerson",
         "objectClass: inetOrgPerson",
         "uid: test",
         "givenName: Test",
         "sn: Test",
         "cn: Test",
         "userPassword: password",
         "",
         "dn: uid=anothertest,o=test",
         "objectClass: top",
         "objectClass: person",
         "objectClass: organizationalPerson",
         "objectClass: inetOrgPerson",
         "uid: anothertest",
         "givenName: Another",
         "sn: Test",
         "cn: Anbother Test",
         "userPassword: password");
    // Try to establish the mapping and get an exception.
    try
    {
      mapper.getEntryForID("test@example.com");
    }
    finally
    {
      mapper.finalizeIdentityMapper();
    }
  }
  /**
   * Tests the {@code getEntryForID} method for a case in which multiple
   * matching entries are identified below different base DNs.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { DirectoryException.class })
  public void testMultipleMatchingEntriesBelowMultipleBases()
         throws Exception
  {
    // Create the identity mapper with an appropriate configuration for this
    // test.
    Entry mapperEntry = TestCaseUtils.makeEntry(
         "dn: cn=Regular Expression,cn=Identity Mappers,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-identity-mapper",
         "objectClass: ds-cfg-regular-expression-identity-mapper",
         "cn: Regular Expression",
         "ds-cfg-identity-mapper-class: " +
              "org.opends.server.extensions.RegularExpressionIdentityMapper",
         "ds-cfg-identity-mapper-enabled: true",
         "ds-cfg-match-attribute: uid",
         "ds-cfg-match-attribute: sn",
         "ds-cfg-match-base-dn: ou=Users 1,o=test",
         "ds-cfg-match-base-dn: ou=Users 2,o=test",
         "ds-cfg-match-pattern: ^([^@]+)@.+$",
         "ds-cfg-replace-pattern: $1");
    RegularExpressionIdentityMapperCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              RegularExpressionIdentityMapperCfgDefn.getInstance(),
              mapperEntry);
    RegularExpressionIdentityMapper mapper =
         new RegularExpressionIdentityMapper();
    assertTrue(mapper.isConfigurationAcceptable(configuration,
                                                new LinkedList<String>()));
    mapper.initializeIdentityMapper(configuration);
    // Create a user entry and add it to the directory.
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntries(
         "dn: ou=Users 1,o=test",
         "objectClass: top",
         "objectClass: organizationalUnit",
         "ou: Users 1",
         "",
         "dn: uid=test,ou=Users 1,o=test",
         "objectClass: top",
         "objectClass: person",
         "objectClass: organizationalPerson",
         "objectClass: inetOrgPerson",
         "uid: test",
         "givenName: Test",
         "sn: Test",
         "cn: Test",
         "userPassword: password",
         "",
         "dn: ou=Users 2,o=test",
         "objectClass: top",
         "objectClass: organizationalUnit",
         "ou: Users 1",
         "",
         "dn: uid=test,ou=Users 2,o=test",
         "objectClass: top",
         "objectClass: person",
         "objectClass: organizationalPerson",
         "objectClass: inetOrgPerson",
         "uid: test",
         "givenName: Test",
         "sn: Test",
         "cn: Test",
         "userPassword: password");
    // Try to establish the mapping and get an exception.
    try
    {
      mapper.getEntryForID("test@example.com");
    }
    finally
    {
      mapper.finalizeIdentityMapper();
    }
  }
}