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

neil_a_wilson
26.40.2007 88db774678ad897f57338a3e1b34a1431ccdd5fd
Add three new certificate mappers to the server:

- One which will take attributes from the certificate subject and map them to
attributes in user entries (Issue #1278).

- One which will search for the subjects of the presented certificates in user
entries (Issue #1279).

- One which will search for the MD5 or SHA1 fingerprints of the presented
certificates in user entries (Issue #1280).
6 files added
5 files modified
5071 ■■■■■ changed files
opends/resource/config/config.ldif 33 ●●●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif 41 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/config/ConfigConstants.java 49 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/FingerprintCertificateMapper.java 760 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/SubjectAttributeToUserAttributeCertificateMapper.java 806 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/SubjectDNToUserAttributeCertificateMapper.java 584 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/ExtensionsMessages.java 525 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/util/StaticUtils.java 31 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/FingerprintCertificateMapperTestCase.java 681 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SubjectAttributeToUserAttributeCertificateMapperTestCase.java 851 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SubjectDNToUserAttributeCertificateMapperTestCase.java 710 ●●●●● patch | view | raw | blame | history
opends/resource/config/config.ldif
@@ -279,10 +279,39 @@
dn: cn=Subject Equals DN,cn=Certificate Mappers,cn=config
objectClass: top
objectClass: ds-cfg-certificate-mapper
cn: Certificate Mapper
cn: Subject Equals DN
ds-cfg-certificate-mapper-class: org.opends.server.extensions.SubjectEqualsDNCertificateMapper
ds-cfg-certificate-mapper-enabled: true
dn: cn=Subject DN to User Attribute,cn=Certificate Mappers,cn=config
objectClass: top
objectClass: ds-cfg-certificate-mapper
objectClass: ds-cfg-subject-dn-to-user-attribute-certificate-mapper
cn: Subject DN to User Attribute
ds-cfg-certificate-mapper-class: org.opends.server.extensions.SubjectDNToUserAttributeCertificateMapper
ds-cfg-certificate-mapper-enabled: true
ds-cfg-certificate-subject-attribute-type: ds-certificate-subject-dn
dn: cn=Subject Attribute to User Attribute,cn=Certificate Mappers,cn=config
objectClass: top
objectClass: ds-cfg-certificate-mapper
objectClass: ds-cfg-subject-attribute-to-user-attribute-certificate-mapper
cn: Subject Attribute to User Attribute
ds-cfg-certificate-mapper-class: org.opends.server.extensions.SubjectAttributeToUserAttributeCertificateMapper
ds-cfg-certificate-mapper-enabled: true
ds-cfg-certificate-subject-attribute-mapping: cn:cn
ds-cfg-certificate-subject-attribute-mapping: e:mail
dn: cn=Fingerprint Mapper,cn=Certificate Mappers,cn=config
objectClass: top
objectClass: ds-cfg-certificate-mapper
objectClass: ds-cfg-fingerprint-certificate-mapper
cn: Fingerprint Mapper
ds-cfg-certificate-mapper-class: org.opends.server.extensions.FingerprintCertificateMapper
ds-cfg-certificate-mapper-enabled: true
ds-cfg-certificate-fingerprint-attribute-type: ds-certificate-fingerprint
ds-cfg-certificate-fingerprint-algorithm: MD5
dn: cn=Connection Handlers,cn=config
objectClass: top
objectClass: ds-cfg-branch
@@ -1587,7 +1616,7 @@
ds-cfg-trust-manager-provider-class: org.opends.server.extensions.FileBasedTrustManagerProvider
ds-cfg-trust-manager-provider-enabled: false
ds-cfg-trust-store-type: PKCS12
ds-cfg-trust-store-file: config/truststore
ds-cfg-trust-store-file: config/truststore.p12
dn: cn=Virtual Attributes,cn=config
objectClass: top
opends/resource/schema/02-config.ldif
@@ -1057,6 +1057,29 @@
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.311
  NAME 'ds-cfg-trust-manager-provider-dn' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
  SINGLE-VALUE X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.312
  NAME 'ds-cfg-certificate-subject-attribute-type'
  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.313
  NAME 'ds-cfg-certificate-user-base-dn' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.314
  NAME 'ds-certificate-subject-dn' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.315
  NAME 'ds-cfg-certificate-subject-attribute-mapping'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.316 NAME 'ds-certificate-fingerprint'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.317
  NAME 'ds-cfg-certificate-fingerprint-attribute-type'
  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.318
  NAME 'ds-cfg-certificate-fingerprint-algorithm'
  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 )
@@ -1449,4 +1472,22 @@
objectClasses: ( 1.3.6.1.4.1.26027.1.2.82 NAME 'ds-cfg-root-dn-base' SUP top
  STRUCTURAL MUST cn MAY ds-cfg-default-root-privilege-name
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.83 NAME 'ds-certificate-user' SUP top
  AUXILIARY MAY ( userCertificate $ ds-certificate-subject-dn $
  ds-certificate-fingerprint ) X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.84
  NAME 'ds-cfg-subject-dn-to-user-attribute-certificate-mapper'
  SUP ds-cfg-certificate-mapper STRUCTURAL
  MUST ds-cfg-certificate-subject-attribute-type
  MAY ds-cfg-certificate-user-base-dn X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.85
  NAME 'ds-cfg-subject-attribute-to-user-attribute-certificate-mapper'
  SUP ds-cfg-certificate-mapper STRUCTURAL
  MUST ds-cfg-certificate-subject-attribute-mapping
  MAY ds-cfg-certificate-user-base-dn X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.86
  NAME 'ds-cfg-fingerprint-certificate-mapper' SUP ds-cfg-certificate-mapper
  STRUCTURAL MUST ( ds-cfg-certificate-fingerprint-attribute-type $
  ds-cfg-certificate-fingerprint-algorithm )
  MAY ds-cfg-certificate-user-base-dn X-ORIGIN 'OpenDS Directory Server' )
opends/src/server/org/opends/server/config/ConfigConstants.java
@@ -480,6 +480,55 @@
  /**
   * The name of the configuration attribute that holds the name of the
   * attribute type that should be used when mapping a certificate fingerprint
   * to a user entry.
   */
  public static final String ATTR_CERTIFICATE_FINGERPRINT_ATTR =
       "ds-cfg-certificate-fingerprint-attribute-type";
  /**
   * The name of the configuration attribute that holds the name of the
   * algorithm that should be used to generate the certificate fingerprint.
   */
  public static final String ATTR_CERTIFICATE_FINGERPRINT_ALGORITHM =
       "ds-cfg-certificate-fingerprint-algorithm";
  /**
   * The name of the configuration attribute that holds the name of the
   * attribute type that should be used when mapping a certificate subject to a
   * user entry.
   */
  public static final String ATTR_CERTIFICATE_SUBJECT_ATTR =
       "ds-cfg-certificate-subject-attribute-type";
  /**
   * The name of the configuration attribute that holds the name of the
   * attribute type that should be used when mapping attributes in a certificate
   * subject to a user entry.
   */
  public static final String ATTR_CERTIFICATE_SUBJECT_ATTR_MAP =
       "ds-cfg-certificate-subject-attribute-mapping";
  /**
   * The name of the configuration attribute that holds the name of the
   * attribute type that should be used when mapping a certificate subject to a
   * user entry.
   */
  public static final String ATTR_CERTIFICATE_SUBJECT_BASEDN =
       "ds-cfg-certificate-user-base-dn";
  /**
   * The name of the configuration attribute that holds the fully-qualified name
   * of the Java class for the certificate mapper implementation.
   */
opends/src/server/org/opends/server/extensions/FingerprintCertificateMapper.java
New file
@@ -0,0 +1,760 @@
/*
 * 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.security.MessageDigest;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import javax.security.auth.x500.X500Principal;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.opends.server.api.CertificateMapper;
import org.opends.server.api.ConfigurableComponent;
import org.opends.server.config.ConfigAttribute;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.config.DNConfigAttribute;
import org.opends.server.config.MultiChoiceConfigAttribute;
import org.opends.server.config.StringConfigAttribute;
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.DirectoryException;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.ConfigChangeResult;
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.config.ConfigConstants.*;
import static org.opends.server.loggers.Debug.*;
import static org.opends.server.messages.ExtensionsMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class implements a very simple Directory Server certificate mapper that
 * will map a certificate to a user only if that user's entry contains an
 * attribute with the fingerprint of the client certificate.  There must be
 * exactly one matching user entry for the mapping to be successful.
 */
public class FingerprintCertificateMapper
       extends CertificateMapper
       implements ConfigurableComponent
{
  /**
   * The fully-qualified name of this class for debugging purposes.
   */
  private static final String CLASS_NAME =
       "org.opends.server.extensions.FingerprintCertificateMapper";
  /**
   * The set of allowed fingerprint algorithms.
   */
  private static final Set<String> FINGERPRINT_ALGORITHMS;
  // The attribute type that will be used to map the certificate's fingerprint.
  private AttributeType fingerprintAttributeType;
  // The DN of the configuration entry for this certificate mapper.
  private DN configEntryDN;
  // The set of base DNs below which the search will be performed.
  private DN[] baseDNs;
  // The algorithm that will be used to generate the fingerprint.
  private String fingerprintAlgorithm;
  static
  {
    LinkedHashSet<String> algorithmSet = new LinkedHashSet<String>(2);
    algorithmSet.add("md5");
    algorithmSet.add("sha1");
    FINGERPRINT_ALGORITHMS = algorithmSet;
  }
  /**
   * Creates a new instance of this certificate mapper.  Note that all actual
   * initialization should be done in the
   * <CODE>initializeCertificateMapper</CODE> method.
   */
  public FingerprintCertificateMapper()
  {
    super();
    assert debugConstructor(CLASS_NAME);
  }
  /**
   * {@inheritDoc}
   */
  public void initializeCertificateMapper(ConfigEntry configEntry)
         throws ConfigException, InitializationException
  {
    assert debugEnter(CLASS_NAME, "initializeCertificateMapper",
                      String.valueOf(configEntry));
    this.configEntryDN = configEntry.getDN();
    // Get the attribute type that will be used to hold the fingerprint.
    int msgID = MSGID_FCM_DESCRIPTION_FINGERPRINT_ATTR;
    StringConfigAttribute attrStub =
         new StringConfigAttribute(ATTR_CERTIFICATE_FINGERPRINT_ATTR,
                                   getMessage(msgID), true, false, false);
    try
    {
      StringConfigAttribute attrAttr =
           (StringConfigAttribute) configEntry.getConfigAttribute(attrStub);
      if (attrAttr == null)
      {
        msgID = MSGID_FCM_NO_FINGERPRINT_ATTR;
        String message = getMessage(msgID, String.valueOf(configEntryDN),
                                    ATTR_CERTIFICATE_FINGERPRINT_ATTR);
        throw new ConfigException(msgID, message);
      }
      else
      {
        String attrName  = attrAttr.pendingValue();
        String lowerName = toLowerCase(attrName);
        fingerprintAttributeType =
             DirectoryServer.getAttributeType(lowerName, false);
        if (fingerprintAttributeType == null)
        {
          msgID = MSGID_FCM_NO_SUCH_ATTR;
          String message = getMessage(msgID, String.valueOf(configEntryDN),
                                      attrName);
          throw new ConfigException(msgID, message);
        }
      }
    }
    catch (ConfigException ce)
    {
      throw ce;
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "initializeCertificateMapper", e);
      msgID = MSGID_FCM_CANNOT_GET_FINGERPRINT_ATTR;
      String message = getMessage(msgID, String.valueOf(configEntryDN),
                                  stackTraceToSingleLineString(e));
      throw new InitializationException(msgID, message, e);
    }
    // Get the fingerprint algorithm.
    msgID = MSGID_FCM_DESCRIPTION_FINGERPRINT_ALGORITHM;
    MultiChoiceConfigAttribute algorithmStub =
         new MultiChoiceConfigAttribute(ATTR_CERTIFICATE_FINGERPRINT_ALGORITHM,
                                        getMessage(msgID), true, false, false,
                                        FINGERPRINT_ALGORITHMS);
    try
    {
      MultiChoiceConfigAttribute algorithmAttr =
           (MultiChoiceConfigAttribute)
           configEntry.getConfigAttribute(algorithmStub);
      if (algorithmAttr == null)
      {
        msgID = MSGID_FCM_NO_FINGERPRINT_ALGORITHM;
        String message = getMessage(msgID, String.valueOf(configEntryDN),
                                    ATTR_CERTIFICATE_FINGERPRINT_ALGORITHM);
        throw new ConfigException(msgID, message);
      }
      else
      {
        fingerprintAlgorithm = algorithmAttr.pendingValue();
      }
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "initializeCertificateMapper", e);
      msgID = MSGID_FCM_CANNOT_GET_FINGERPRINT_ALGORITHM;
      String message = getMessage(msgID, String.valueOf(configEntryDN),
                                  stackTraceToSingleLineString(e));
      throw new InitializationException(msgID, message, e);
    }
    // Get the set of base DNs below which to perform the searches.
    baseDNs = null;
    msgID = MSGID_FCM_DESCRIPTION_BASE_DN;
    DNConfigAttribute baseStub =
         new DNConfigAttribute(ATTR_CERTIFICATE_SUBJECT_BASEDN,
                               getMessage(msgID), false, true, false);
    try
    {
      DNConfigAttribute baseAttr =
           (DNConfigAttribute) configEntry.getConfigAttribute(baseStub);
      if (baseAttr != null)
      {
        List<DN> dnList = baseAttr.activeValues();
        baseDNs = new DN[dnList.size()];
        dnList.toArray(baseDNs);
      }
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "initializeCertificateMapper", e);
      msgID = MSGID_FCM_CANNOT_GET_BASE_DN;
      String message = getMessage(msgID, String.valueOf(configEntryDN),
                                  stackTraceToSingleLineString(e));
      throw new InitializationException(msgID, message, e);
    }
    DirectoryServer.registerConfigurableComponent(this);
  }
  /**
   * {@inheritDoc}
   */
  public void finalizeCertificateMapper()
  {
    assert debugEnter(CLASS_NAME, "finalizeCertificateMapper");
    DirectoryServer.deregisterConfigurableComponent(this);
  }
  /**
   * {@inheritDoc}
   */
  public Entry mapCertificateToUser(Certificate[] certificateChain)
         throws DirectoryException
  {
    assert debugEnter(CLASS_NAME, "mapCertificateToUser",
                      String.valueOf(certificateChain));
    // Make sure that a peer certificate was provided.
    if ((certificateChain == null) || (certificateChain.length == 0))
    {
      int    msgID   = MSGID_FCM_NO_PEER_CERTIFICATE;
      String message = getMessage(msgID);
      throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, message,
                                   msgID);
    }
    // Get the first certificate in the chain.  It must be an X.509 certificate.
    X509Certificate peerCertificate;
    try
    {
      peerCertificate = (X509Certificate) certificateChain[0];
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "mapCertificateToUser", e);
      int    msgID   = MSGID_FCM_PEER_CERT_NOT_X509;
      String message =
           getMessage(msgID, String.valueOf(certificateChain[0].getType()));
      throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, message,
                                   msgID);
    }
    // Get the signature from the peer certificate and create a digest of it
    // using the configured algorithm.
    String fingerprintString;
    try
    {
      MessageDigest digest = MessageDigest.getInstance(fingerprintAlgorithm);
      byte[] fingerprintBytes = digest.digest(peerCertificate.getEncoded());
      fingerprintString = bytesToColonDelimitedHex(fingerprintBytes);
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "mapCertificateToUser", e);
      String peerSubject = peerCertificate.getSubjectX500Principal().getName(
                                X500Principal.RFC2253);
      int    msgID   = MSGID_FCM_CANNOT_CALCULATE_FINGERPRINT;
      String message = getMessage(msgID, peerSubject,
                                  stackTraceToSingleLineString(e));
      throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, message,
                                   msgID);
    }
    // Create the search filter from the fingerprint.
    AttributeValue value =
         new AttributeValue(fingerprintAttributeType, fingerprintString);
    SearchFilter filter =
         SearchFilter.createEqualityFilter(fingerprintAttributeType, value);
    // If we have an explicit set of base DNs, then use it.  Otherwise, use the
    // set of public naming contexts in the server.
    DN[] bases = baseDNs;
    if (bases == null)
    {
      bases = new DN[0];
      bases = DirectoryServer.getPublicNamingContexts().keySet().toArray(bases);
    }
    // For each base DN, issue an internal search in an attempt to map the
    // certificate.
    Entry userEntry = null;
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    for (DN baseDN : bases)
    {
      InternalSearchOperation searchOperation =
           conn.processSearch(baseDN, SearchScope.WHOLE_SUBTREE, filter);
      for (SearchResultEntry entry : searchOperation.getSearchEntries())
      {
        if (userEntry == null)
        {
          userEntry = entry;
        }
        else
        {
          int    msgID   = MSGID_FCM_MULTIPLE_MATCHING_ENTRIES;
          String message = getMessage(msgID, fingerprintString,
                                      String.valueOf(userEntry.getDN()),
                                      String.valueOf(entry.getDN()));
          throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, message,
                                       msgID);
        }
      }
    }
    // If we've gotten here, then we either found exactly one user entry or we
    // didn't find any.  Either way, return the entry or null to the caller.
    return userEntry;
  }
  /**
   * Retrieves the DN of the configuration entry with which this
   * component is associated.
   *
   * @return  The DN of the configuration entry with which this
   *          component is associated.
   */
  public DN getConfigurableComponentEntryDN()
  {
    assert debugEnter(CLASS_NAME, "getConfigurableComponentEntryDN");
    return configEntryDN;
  }
  /**
   * Retrieves the set of configuration attributes that are associated
   * with this configurable component.
   *
   * @return  The set of configuration attributes that are associated
   *          with this configurable component.
   */
  public List<ConfigAttribute> getConfigurationAttributes()
  {
    assert debugEnter(CLASS_NAME, "getConfigurationAttributes");
    LinkedList<ConfigAttribute> attrList = new LinkedList<ConfigAttribute>();
    int msgID = MSGID_FCM_DESCRIPTION_FINGERPRINT_ATTR;
    attrList.add(new StringConfigAttribute(ATTR_CERTIFICATE_SUBJECT_ATTR,
                          getMessage(msgID), true, false, false,
                          fingerprintAttributeType.getNameOrOID()));
    msgID = MSGID_FCM_DESCRIPTION_FINGERPRINT_ALGORITHM;
    attrList.add(new MultiChoiceConfigAttribute(
                          ATTR_CERTIFICATE_FINGERPRINT_ALGORITHM,
                          getMessage(msgID), true, false, false,
                          FINGERPRINT_ALGORITHMS, fingerprintAlgorithm));
    LinkedList<DN> dnList = new LinkedList<DN>();
    if (baseDNs != null)
    {
      for (DN baseDN : baseDNs)
      {
        dnList.add(baseDN);
      }
    }
    msgID = MSGID_FCM_DESCRIPTION_BASE_DN;
    attrList.add(new DNConfigAttribute(ATTR_CERTIFICATE_SUBJECT_BASEDN,
                                       getMessage(msgID), false, true, false,
                                       dnList));
    return attrList;
  }
  /**
   * Indicates whether the provided configuration entry has an
   * acceptable configuration for this component.  If it does not,
   * then detailed information about the problem(s) should be added to
   * the provided list.
   *
   * @param  configEntry          The configuration entry for which to
   *                              make the determination.
   * @param  unacceptableReasons  A list that can be used to hold
   *                              messages about why the provided
   *                              entry does not have an acceptable
   *                              configuration.
   *
   * @return  <CODE>true</CODE> if the provided entry has an
   *          acceptable configuration for this component, or
   *          <CODE>false</CODE> if not.
   */
  public boolean hasAcceptableConfiguration(ConfigEntry configEntry,
                                            List<String> unacceptableReasons)
  {
    assert debugEnter(CLASS_NAME, "hasAcceptableConfiguration",
                      String.valueOf(configEntry), "java.util.List<String>");
    DN configEntryDN = configEntry.getDN();
    boolean configAcceptable = true;
    // Get the attribute type that will be used to hold the fingerprint.
    AttributeType newFingerprintType = null;
    int msgID = MSGID_FCM_DESCRIPTION_FINGERPRINT_ATTR;
    StringConfigAttribute attrStub =
         new StringConfigAttribute(ATTR_CERTIFICATE_FINGERPRINT_ATTR,
                                   getMessage(msgID), true, false, false);
    try
    {
      StringConfigAttribute attrAttr =
           (StringConfigAttribute) configEntry.getConfigAttribute(attrStub);
      if (attrAttr == null)
      {
        msgID = MSGID_FCM_NO_FINGERPRINT_ATTR;
        String message = getMessage(msgID, String.valueOf(configEntryDN),
                                    ATTR_CERTIFICATE_FINGERPRINT_ATTR);
        unacceptableReasons.add(message);
        configAcceptable = false;
      }
      else
      {
        String attrName  = attrAttr.pendingValue();
        String lowerName = toLowerCase(attrName);
        newFingerprintType =
             DirectoryServer.getAttributeType(lowerName, false);
        if (newFingerprintType == null)
        {
          msgID = MSGID_FCM_NO_SUCH_ATTR;
          String message = getMessage(msgID, String.valueOf(configEntryDN),
                                      attrName);
        unacceptableReasons.add(message);
        configAcceptable = false;
        }
      }
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "initializeCertificateMapper", e);
      msgID = MSGID_FCM_CANNOT_GET_FINGERPRINT_ATTR;
      String message = getMessage(msgID, String.valueOf(configEntryDN),
                                  stackTraceToSingleLineString(e));
      unacceptableReasons.add(message);
      configAcceptable = false;
    }
    // Get the fingerprint algorithm.
    String newFingerprintAlgorithm = null;
    msgID = MSGID_FCM_DESCRIPTION_FINGERPRINT_ALGORITHM;
    MultiChoiceConfigAttribute algorithmStub =
         new MultiChoiceConfigAttribute(ATTR_CERTIFICATE_FINGERPRINT_ALGORITHM,
                                        getMessage(msgID), true, false, false,
                                        FINGERPRINT_ALGORITHMS);
    try
    {
      MultiChoiceConfigAttribute algorithmAttr =
           (MultiChoiceConfigAttribute)
           configEntry.getConfigAttribute(algorithmStub);
      if (algorithmAttr == null)
      {
        msgID = MSGID_FCM_NO_FINGERPRINT_ALGORITHM;
        String message = getMessage(msgID, String.valueOf(configEntryDN),
                                    ATTR_CERTIFICATE_FINGERPRINT_ALGORITHM);
        unacceptableReasons.add(message);
        configAcceptable = false;
      }
      else
      {
        newFingerprintAlgorithm = algorithmAttr.pendingValue();
      }
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "initializeCertificateMapper", e);
      msgID = MSGID_FCM_CANNOT_GET_FINGERPRINT_ALGORITHM;
      String message = getMessage(msgID, String.valueOf(configEntryDN),
                                  stackTraceToSingleLineString(e));
      unacceptableReasons.add(message);
      configAcceptable = false;
    }
    // Get the set of base DNs below which to perform the searches.
    DN[] newBaseDNs = null;
    msgID = MSGID_FCM_DESCRIPTION_BASE_DN;
    DNConfigAttribute baseStub =
         new DNConfigAttribute(ATTR_CERTIFICATE_SUBJECT_BASEDN,
                               getMessage(msgID), false, true, false);
    try
    {
      DNConfigAttribute baseAttr =
           (DNConfigAttribute) configEntry.getConfigAttribute(baseStub);
      if (baseAttr != null)
      {
        List<DN> dnList = baseAttr.activeValues();
        newBaseDNs = new DN[dnList.size()];
        dnList.toArray(newBaseDNs);
      }
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "initializeCertificateMapper", e);
      msgID = MSGID_FCM_CANNOT_GET_BASE_DN;
      String message = getMessage(msgID, String.valueOf(configEntryDN),
                                  stackTraceToSingleLineString(e));
      unacceptableReasons.add(message);
      configAcceptable = false;
    }
    return configAcceptable;
  }
  /**
   * Makes a best-effort attempt to apply the configuration contained
   * in the provided entry.  Information about the result of this
   * processing should be added to the provided message list.
   * Information should always be added to this list if a
   * configuration change could not be applied.  If detailed results
   * are requested, then information about the changes applied
   * successfully (and optionally about parameters that were not
   * changed) should also be included.
   *
   * @param  configEntry      The entry containing the new
   *                          configuration to apply for this
   *                          component.
   * @param  detailedResults  Indicates whether detailed information
   *                          about the processing should be added to
   *                          the list.
   *
   * @return  Information about the result of the configuration
   *          update.
   */
  public ConfigChangeResult applyNewConfiguration(ConfigEntry configEntry,
                                                  boolean detailedResults)
  {
    assert debugEnter(CLASS_NAME, "applyNewConfiguration",
                      String.valueOf(configEntry),
                      String.valueOf(detailedResults));
    DN                configEntryDN       = configEntry.getDN();
    ResultCode        resultCode          = ResultCode.SUCCESS;
    ArrayList<String> messages            = new ArrayList<String>();
    boolean           adminActionRequired = false;
    // Get the attribute type that will be used to hold the fingerprint.
    AttributeType newFingerprintType = null;
    int msgID = MSGID_FCM_DESCRIPTION_FINGERPRINT_ATTR;
    StringConfigAttribute attrStub =
         new StringConfigAttribute(ATTR_CERTIFICATE_FINGERPRINT_ATTR,
                                   getMessage(msgID), true, false, false);
    try
    {
      StringConfigAttribute attrAttr =
           (StringConfigAttribute) configEntry.getConfigAttribute(attrStub);
      if (attrAttr == null)
      {
        if (resultCode == ResultCode.SUCCESS)
        {
          resultCode = ResultCode.OBJECTCLASS_VIOLATION;
        }
        msgID = MSGID_FCM_NO_FINGERPRINT_ATTR;
        messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                                ATTR_CERTIFICATE_FINGERPRINT_ATTR));
      }
      else
      {
        String attrName  = attrAttr.pendingValue();
        String lowerName = toLowerCase(attrName);
        newFingerprintType =
             DirectoryServer.getAttributeType(lowerName, false);
        if (newFingerprintType == null)
        {
          if (resultCode == ResultCode.SUCCESS)
          {
            resultCode = ResultCode.NO_SUCH_ATTRIBUTE;
          }
          msgID = MSGID_FCM_NO_SUCH_ATTR;
          messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                                  attrName));
        }
      }
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "initializeCertificateMapper", e);
      if (resultCode == ResultCode.SUCCESS)
      {
        resultCode = DirectoryServer.getServerErrorResultCode();
      }
      msgID = MSGID_FCM_CANNOT_GET_FINGERPRINT_ATTR;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                              stackTraceToSingleLineString(e)));
    }
    // Get the fingerprint algorithm.
    String newFingerprintAlgorithm = null;
    msgID = MSGID_FCM_DESCRIPTION_FINGERPRINT_ALGORITHM;
    MultiChoiceConfigAttribute algorithmStub =
         new MultiChoiceConfigAttribute(ATTR_CERTIFICATE_FINGERPRINT_ALGORITHM,
                                        getMessage(msgID), true, false, false,
                                        FINGERPRINT_ALGORITHMS);
    try
    {
      MultiChoiceConfigAttribute algorithmAttr =
           (MultiChoiceConfigAttribute)
           configEntry.getConfigAttribute(algorithmStub);
      if (algorithmAttr == null)
      {
        if (resultCode == ResultCode.SUCCESS)
        {
          resultCode = ResultCode.OBJECTCLASS_VIOLATION;
        }
        msgID = MSGID_FCM_NO_FINGERPRINT_ALGORITHM;
        messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                                ATTR_CERTIFICATE_FINGERPRINT_ALGORITHM));
      }
      else
      {
        newFingerprintAlgorithm = algorithmAttr.pendingValue();
      }
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "initializeCertificateMapper", e);
      if (resultCode == ResultCode.SUCCESS)
      {
        resultCode = DirectoryServer.getServerErrorResultCode();
      }
      msgID = MSGID_FCM_CANNOT_GET_FINGERPRINT_ALGORITHM;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                              stackTraceToSingleLineString(e)));
    }
    // Get the set of base DNs below which to perform the searches.
    DN[] newBaseDNs = null;
    msgID = MSGID_FCM_DESCRIPTION_BASE_DN;
    DNConfigAttribute baseStub =
         new DNConfigAttribute(ATTR_CERTIFICATE_SUBJECT_BASEDN,
                               getMessage(msgID), false, true, false);
    try
    {
      DNConfigAttribute baseAttr =
           (DNConfigAttribute) configEntry.getConfigAttribute(baseStub);
      if (baseAttr != null)
      {
        List<DN> dnList = baseAttr.activeValues();
        newBaseDNs = new DN[dnList.size()];
        dnList.toArray(newBaseDNs);
      }
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "initializeCertificateMapper", e);
      if (resultCode == ResultCode.SUCCESS)
      {
        resultCode = DirectoryServer.getServerErrorResultCode();
      }
      msgID = MSGID_FCM_CANNOT_GET_BASE_DN;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                              stackTraceToSingleLineString(e)));
    }
    if (resultCode == ResultCode.SUCCESS)
    {
      fingerprintAttributeType = newFingerprintType;
      fingerprintAlgorithm     = newFingerprintAlgorithm;
      baseDNs                  = newBaseDNs;
    }
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
  }
}
opends/src/server/org/opends/server/extensions/SubjectAttributeToUserAttributeCertificateMapper.java
New file
@@ -0,0 +1,806 @@
/*
 * 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.security.cert.Certificate;
import java.security.cert.X509Certificate;
import javax.security.auth.x500.X500Principal;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import org.opends.server.api.CertificateMapper;
import org.opends.server.api.ConfigurableComponent;
import org.opends.server.config.ConfigAttribute;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.config.DNConfigAttribute;
import org.opends.server.config.StringConfigAttribute;
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.DirectoryException;
import org.opends.server.types.AttributeType;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import org.opends.server.types.RDN;
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.config.ConfigConstants.*;
import static org.opends.server.loggers.Debug.*;
import static org.opends.server.messages.ExtensionsMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class implements a very simple Directory Server certificate mapper that
 * will map a certificate to a user based on attributes contained in both the
 * certificate subject and the user's entry.  The configuration may include
 * mappings from certificate attributes to attributes in user entries, and all
 * of those certificate attributes that are present in the subject will be used
 * to search for matching user entries.
 */
public class SubjectAttributeToUserAttributeCertificateMapper
       extends CertificateMapper
       implements ConfigurableComponent
{
  /**
   * The fully-qualified name of this class for debugging purposes.
   */
  private static final String CLASS_NAME =
       "org.opends.server.extensions." +
            "SubjectAttributeToUserAttributeCertificateMapper";
  // The DN of the configuration entry for this certificate mapper.
  private DN configEntryDN;
  // The set of base DNs below which the search will be performed.
  private DN[] baseDNs;
  // The mappings between certificate attribute names and user attribute types.
  private LinkedHashMap<String,AttributeType> attributeMap;
  /**
   * Creates a new instance of this certificate mapper.  Note that all actual
   * initialization should be done in the
   * <CODE>initializeCertificateMapper</CODE> method.
   */
  public SubjectAttributeToUserAttributeCertificateMapper()
  {
    super();
    assert debugConstructor(CLASS_NAME);
  }
  /**
   * {@inheritDoc}
   */
  public void initializeCertificateMapper(ConfigEntry configEntry)
         throws ConfigException, InitializationException
  {
    assert debugEnter(CLASS_NAME, "initializeCertificateMapper",
                      String.valueOf(configEntry));
    this.configEntryDN = configEntry.getDN();
    // Get the attribute that will be used to map subject attributes to user
    // attributes.
    attributeMap = new LinkedHashMap<String,AttributeType>();
    int msgID = MSGID_SATUACM_DESCRIPTION_ATTR_MAP;
    StringConfigAttribute mapStub =
         new StringConfigAttribute(ATTR_CERTIFICATE_SUBJECT_ATTR_MAP,
                                   getMessage(msgID), true, true, false);
    try
    {
      StringConfigAttribute mapAttr =
           (StringConfigAttribute) configEntry.getConfigAttribute(mapStub);
      if (mapAttr == null)
      {
        msgID = MSGID_SATUACM_NO_MAP_ATTR;
        String message = getMessage(msgID, String.valueOf(configEntryDN),
                                    ATTR_CERTIFICATE_SUBJECT_ATTR_MAP);
        throw new ConfigException(msgID, message);
      }
      else
      {
        for (String mapStr : mapAttr.pendingValues())
        {
          String lowerMap = toLowerCase(mapStr);
          int colonPos = lowerMap.indexOf(':');
          if (colonPos <= 0)
          {
            msgID = MSGID_SATUACM_INVALID_MAP_FORMAT;
            String message = getMessage(msgID, String.valueOf(configEntryDN),
                                        mapStr);
            throw new ConfigException(msgID, message);
          }
          String certAttrName = lowerMap.substring(0, colonPos).trim();
          String userAttrName = lowerMap.substring(colonPos+1).trim();
          if ((certAttrName.length() == 0) || (userAttrName.length() == 0))
          {
            msgID = MSGID_SATUACM_INVALID_MAP_FORMAT;
            String message = getMessage(msgID, String.valueOf(configEntryDN),
                                        mapStr);
            throw new ConfigException(msgID, message);
          }
          if (attributeMap.containsKey(certAttrName))
          {
            msgID = MSGID_SATUACM_DUPLICATE_CERT_ATTR;
            String message = getMessage(msgID, String.valueOf(configEntryDN),
                                        certAttrName);
            throw new ConfigException(msgID, message);
          }
          AttributeType userAttrType =
               DirectoryServer.getAttributeType(userAttrName, false);
          if (userAttrType == null)
          {
            msgID = MSGID_SATUACM_NO_SUCH_ATTR;
            String message = getMessage(msgID, mapStr,
                                        String.valueOf(configEntryDN),
                                        userAttrName);
            throw new ConfigException(msgID, message);
          }
          for (AttributeType attrType : attributeMap.values())
          {
            if (attrType.equals(userAttrType))
            {
              msgID = MSGID_SATUACM_DUPLICATE_USER_ATTR;
              String message = getMessage(msgID, String.valueOf(configEntryDN),
                                          attrType.getNameOrOID());
              throw new ConfigException(msgID, message);
            }
          }
          attributeMap.put(certAttrName, userAttrType);
        }
      }
    }
    catch (ConfigException ce)
    {
      throw ce;
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "initializeCertificateMapper", e);
      msgID = MSGID_SATUACM_CANNOT_GET_ATTR_MAP;
      String message = getMessage(msgID, String.valueOf(configEntryDN),
                                  stackTraceToSingleLineString(e));
      throw new InitializationException(msgID, message, e);
    }
    // Get the set of base DNs below which to perform the searches.
    baseDNs = null;
    msgID = MSGID_SATUACM_DESCRIPTION_BASE_DN;
    DNConfigAttribute baseStub =
         new DNConfigAttribute(ATTR_CERTIFICATE_SUBJECT_BASEDN,
                               getMessage(msgID), false, true, false);
    try
    {
      DNConfigAttribute baseAttr =
           (DNConfigAttribute) configEntry.getConfigAttribute(baseStub);
      if (baseAttr != null)
      {
        List<DN> dnList = baseAttr.activeValues();
        baseDNs = new DN[dnList.size()];
        dnList.toArray(baseDNs);
      }
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "initializeCertificateMapper", e);
      msgID = MSGID_SATUACM_CANNOT_GET_BASE_DN;
      String message = getMessage(msgID, String.valueOf(configEntryDN),
                                  stackTraceToSingleLineString(e));
      throw new InitializationException(msgID, message, e);
    }
    DirectoryServer.registerConfigurableComponent(this);
  }
  /**
   * {@inheritDoc}
   */
  public void finalizeCertificateMapper()
  {
    assert debugEnter(CLASS_NAME, "finalizeCertificateMapper");
    DirectoryServer.deregisterConfigurableComponent(this);
  }
  /**
   * {@inheritDoc}
   */
  public Entry mapCertificateToUser(Certificate[] certificateChain)
         throws DirectoryException
  {
    assert debugEnter(CLASS_NAME, "mapCertificateToUser",
                      String.valueOf(certificateChain));
    // Make sure that a peer certificate was provided.
    if ((certificateChain == null) || (certificateChain.length == 0))
    {
      int    msgID   = MSGID_SATUACM_NO_PEER_CERTIFICATE;
      String message = getMessage(msgID);
      throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, message,
                                   msgID);
    }
    // Get the first certificate in the chain.  It must be an X.509 certificate.
    X509Certificate peerCertificate;
    try
    {
      peerCertificate = (X509Certificate) certificateChain[0];
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "mapCertificateToUser", e);
      int    msgID   = MSGID_SATUACM_PEER_CERT_NOT_X509;
      String message =
           getMessage(msgID, String.valueOf(certificateChain[0].getType()));
      throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, message,
                                   msgID);
    }
    // Get the subject from the peer certificate and use it to create a search
    // filter.
    DN peerDN;
    X500Principal peerPrincipal = peerCertificate.getSubjectX500Principal();
    String peerName = peerPrincipal.getName(X500Principal.RFC2253);
    try
    {
      peerDN = DN.decode(peerName);
    }
    catch (DirectoryException de)
    {
      int    msgID   = MSGID_SATUACM_CANNOT_DECODE_SUBJECT_AS_DN;
      String message = getMessage(msgID, peerName, de.getErrorMessage());
      throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, message,
                                   msgID, de);
    }
    LinkedList<SearchFilter> filterComps = new LinkedList<SearchFilter>();
    for (int i=0; i < peerDN.getNumComponents(); i++)
    {
      RDN rdn = peerDN.getRDN(i);
      for (int j=0; j < rdn.getNumValues(); j++)
      {
        String lowerName = toLowerCase(rdn.getAttributeName(j));
        AttributeType attrType = attributeMap.get(lowerName);
        if (attrType != null)
        {
          filterComps.add(SearchFilter.createEqualityFilter(attrType,
                                            rdn.getAttributeValue(j)));
        }
      }
    }
    if (filterComps.isEmpty())
    {
      int    msgID   = MSGID_SATUACM_NO_MAPPABLE_ATTRIBUTES;
      String message = getMessage(msgID, peerName);
      throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, message,
                                   msgID);
    }
    SearchFilter filter = SearchFilter.createANDFilter(filterComps);
    // If we have an explicit set of base DNs, then use it.  Otherwise, use the
    // set of public naming contexts in the server.
    DN[] bases = baseDNs;
    if (bases == null)
    {
      bases = new DN[0];
      bases = DirectoryServer.getPublicNamingContexts().keySet().toArray(bases);
    }
    // For each base DN, issue an internal search in an attempt to map the
    // certificate.
    Entry userEntry = null;
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    for (DN baseDN : bases)
    {
      InternalSearchOperation searchOperation =
           conn.processSearch(baseDN, SearchScope.WHOLE_SUBTREE, filter);
      for (SearchResultEntry entry : searchOperation.getSearchEntries())
      {
        if (userEntry == null)
        {
          userEntry = entry;
        }
        else
        {
          int    msgID   = MSGID_SATUACM_MULTIPLE_MATCHING_ENTRIES;
          String message = getMessage(msgID, peerName,
                                      String.valueOf(userEntry.getDN()),
                                      String.valueOf(entry.getDN()));
          throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, message,
                                       msgID);
        }
      }
    }
    // If we've gotten here, then we either found exactly one user entry or we
    // didn't find any.  Either way, return the entry or null to the caller.
    return userEntry;
  }
  /**
   * Retrieves the DN of the configuration entry with which this
   * component is associated.
   *
   * @return  The DN of the configuration entry with which this
   *          component is associated.
   */
  public DN getConfigurableComponentEntryDN()
  {
    assert debugEnter(CLASS_NAME, "getConfigurableComponentEntryDN");
    return configEntryDN;
  }
  /**
   * Retrieves the set of configuration attributes that are associated
   * with this configurable component.
   *
   * @return  The set of configuration attributes that are associated
   *          with this configurable component.
   */
  public List<ConfigAttribute> getConfigurationAttributes()
  {
    assert debugEnter(CLASS_NAME, "getConfigurationAttributes");
    LinkedList<ConfigAttribute> attrList = new LinkedList<ConfigAttribute>();
    LinkedList<String> mapValues = new LinkedList<String>();
    for (String certAttrName : attributeMap.keySet())
    {
      AttributeType certAttrType = attributeMap.get(certAttrName);
      mapValues.add(certAttrName + ":" + certAttrType.getNameOrOID());
    }
    int msgID = MSGID_SATUACM_DESCRIPTION_ATTR_MAP;
    attrList.add(new StringConfigAttribute(ATTR_CERTIFICATE_SUBJECT_ATTR_MAP,
                                           getMessage(msgID), true, false,
                                           false, mapValues));
    LinkedList<DN> dnList = new LinkedList<DN>();
    if (baseDNs != null)
    {
      for (DN baseDN : baseDNs)
      {
        dnList.add(baseDN);
      }
    }
    msgID = MSGID_SATUACM_DESCRIPTION_BASE_DN;
    attrList.add(new DNConfigAttribute(ATTR_CERTIFICATE_SUBJECT_BASEDN,
                                       getMessage(msgID), false, true, false,
                                       dnList));
    return attrList;
  }
  /**
   * Indicates whether the provided configuration entry has an
   * acceptable configuration for this component.  If it does not,
   * then detailed information about the problem(s) should be added to
   * the provided list.
   *
   * @param  configEntry          The configuration entry for which to
   *                              make the determination.
   * @param  unacceptableReasons  A list that can be used to hold
   *                              messages about why the provided
   *                              entry does not have an acceptable
   *                              configuration.
   *
   * @return  <CODE>true</CODE> if the provided entry has an
   *          acceptable configuration for this component, or
   *          <CODE>false</CODE> if not.
   */
  public boolean hasAcceptableConfiguration(ConfigEntry configEntry,
                                            List<String> unacceptableReasons)
  {
    assert debugEnter(CLASS_NAME, "hasAcceptableConfiguration",
                      String.valueOf(configEntry), "java.util.List<String>");
    DN configEntryDN = configEntry.getDN();
    boolean configAcceptable = true;
    // Get the attribute that will be used to map subject attributes to user
    // attributes.
    LinkedHashMap<String,AttributeType> newAttributeMap =
         new LinkedHashMap<String,AttributeType>();
    int msgID = MSGID_SATUACM_DESCRIPTION_ATTR_MAP;
    StringConfigAttribute mapStub =
         new StringConfigAttribute(ATTR_CERTIFICATE_SUBJECT_ATTR_MAP,
                                   getMessage(msgID), true, true, false);
    try
    {
      StringConfigAttribute mapAttr =
           (StringConfigAttribute) configEntry.getConfigAttribute(mapStub);
      if (mapAttr == null)
      {
        msgID = MSGID_SATUACM_NO_MAP_ATTR;
        String message = getMessage(msgID, String.valueOf(configEntryDN),
                                    ATTR_CERTIFICATE_SUBJECT_ATTR_MAP);
        unacceptableReasons.add(message);
        configAcceptable = false;
      }
      else
      {
mapLoop:
        for (String mapStr : mapAttr.pendingValues())
        {
          String lowerMap = toLowerCase(mapStr);
          int colonPos = lowerMap.indexOf(':');
          if (colonPos <= 0)
          {
            msgID = MSGID_SATUACM_INVALID_MAP_FORMAT;
            String message = getMessage(msgID, String.valueOf(configEntryDN),
                                        mapStr);
            unacceptableReasons.add(message);
            configAcceptable = false;
            continue;
          }
          String certAttrName = lowerMap.substring(0, colonPos).trim();
          String userAttrName = lowerMap.substring(colonPos+1).trim();
          if ((certAttrName.length() == 0) || (userAttrName.length() == 0))
          {
            msgID = MSGID_SATUACM_INVALID_MAP_FORMAT;
            String message = getMessage(msgID, String.valueOf(configEntryDN),
                                        mapStr);
            unacceptableReasons.add(message);
            configAcceptable = false;
            continue;
          }
          if (newAttributeMap.containsKey(certAttrName))
          {
            msgID = MSGID_SATUACM_DUPLICATE_CERT_ATTR;
            String message = getMessage(msgID, String.valueOf(configEntryDN),
                                        certAttrName);
            unacceptableReasons.add(message);
            configAcceptable = false;
            continue;
          }
          AttributeType userAttrType =
               DirectoryServer.getAttributeType(userAttrName, false);
          if (userAttrType == null)
          {
            msgID = MSGID_SATUACM_NO_SUCH_ATTR;
            String message = getMessage(msgID, mapStr,
                                        String.valueOf(configEntryDN),
                                        userAttrName);
            unacceptableReasons.add(message);
            configAcceptable = false;
            continue;
          }
          for (AttributeType attrType : newAttributeMap.values())
          {
            if (attrType.equals(userAttrType))
            {
              msgID = MSGID_SATUACM_DUPLICATE_USER_ATTR;
              String message = getMessage(msgID, String.valueOf(configEntryDN),
                                          attrType.getNameOrOID());
              unacceptableReasons.add(message);
              configAcceptable = false;
              continue mapLoop;
            }
          }
          newAttributeMap.put(certAttrName, userAttrType);
        }
      }
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "hasAcceptableConfiguration", e);
      msgID = MSGID_SATUACM_CANNOT_GET_ATTR_MAP;
      String message = getMessage(msgID, String.valueOf(configEntryDN),
                                  stackTraceToSingleLineString(e));
      unacceptableReasons.add(message);
      configAcceptable = false;
    }
    // Get the set of base DNs below which to perform the searches.
    DN[] newBaseDNs = null;
    msgID = MSGID_SATUACM_DESCRIPTION_BASE_DN;
    DNConfigAttribute baseStub =
         new DNConfigAttribute(ATTR_CERTIFICATE_SUBJECT_BASEDN,
                               getMessage(msgID), false, true, false);
    try
    {
      DNConfigAttribute baseAttr =
           (DNConfigAttribute) configEntry.getConfigAttribute(baseStub);
      if (baseAttr != null)
      {
        List<DN> dnList = baseAttr.activeValues();
        newBaseDNs = new DN[dnList.size()];
        dnList.toArray(newBaseDNs);
      }
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "hasAcceptableConfiguration", e);
      msgID = MSGID_SATUACM_CANNOT_GET_BASE_DN;
      String message = getMessage(msgID, String.valueOf(configEntryDN),
                                  stackTraceToSingleLineString(e));
      unacceptableReasons.add(message);
      configAcceptable = false;
    }
    return configAcceptable;
  }
  /**
   * Makes a best-effort attempt to apply the configuration contained
   * in the provided entry.  Information about the result of this
   * processing should be added to the provided message list.
   * Information should always be added to this list if a
   * configuration change could not be applied.  If detailed results
   * are requested, then information about the changes applied
   * successfully (and optionally about parameters that were not
   * changed) should also be included.
   *
   * @param  configEntry      The entry containing the new
   *                          configuration to apply for this
   *                          component.
   * @param  detailedResults  Indicates whether detailed information
   *                          about the processing should be added to
   *                          the list.
   *
   * @return  Information about the result of the configuration
   *          update.
   */
  public ConfigChangeResult applyNewConfiguration(ConfigEntry configEntry,
                                                  boolean detailedResults)
  {
    assert debugEnter(CLASS_NAME, "applyNewConfiguration",
                      String.valueOf(configEntry),
                      String.valueOf(detailedResults));
    DN                configEntryDN       = configEntry.getDN();
    ResultCode        resultCode          = ResultCode.SUCCESS;
    ArrayList<String> messages            = new ArrayList<String>();
    boolean           adminActionRequired = false;
    // Get the attribute that will be used to map subject attributes to user
    // attributes.
    LinkedHashMap<String,AttributeType> newAttributeMap =
         new LinkedHashMap<String,AttributeType>();
    int msgID = MSGID_SATUACM_DESCRIPTION_ATTR_MAP;
    StringConfigAttribute mapStub =
         new StringConfigAttribute(ATTR_CERTIFICATE_SUBJECT_ATTR_MAP,
                                   getMessage(msgID), true, true, false);
    try
    {
      StringConfigAttribute mapAttr =
           (StringConfigAttribute) configEntry.getConfigAttribute(mapStub);
      if (mapAttr == null)
      {
        if (resultCode == ResultCode.SUCCESS)
        {
          resultCode = ResultCode.OBJECTCLASS_VIOLATION;
        }
        msgID = MSGID_SATUACM_NO_MAP_ATTR;
        messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                                ATTR_CERTIFICATE_SUBJECT_ATTR_MAP));
      }
      else
      {
mapLoop:
        for (String mapStr : mapAttr.pendingValues())
        {
          String lowerMap = toLowerCase(mapStr);
          int colonPos = lowerMap.indexOf(':');
          if (colonPos <= 0)
          {
            if (resultCode == ResultCode.SUCCESS)
            {
              resultCode = ResultCode.INVALID_ATTRIBUTE_SYNTAX;
            }
            msgID = MSGID_SATUACM_INVALID_MAP_FORMAT;
            messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                                    mapStr));
            break;
          }
          String certAttrName = lowerMap.substring(0, colonPos).trim();
          String userAttrName = lowerMap.substring(colonPos+1).trim();
          if ((certAttrName.length() == 0) || (userAttrName.length() == 0))
          {
            if (resultCode == ResultCode.SUCCESS)
            {
              resultCode = ResultCode.INVALID_ATTRIBUTE_SYNTAX;
            }
            msgID = MSGID_SATUACM_INVALID_MAP_FORMAT;
            messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                                    mapStr));
            break;
          }
          if (newAttributeMap.containsKey(certAttrName))
          {
            if (resultCode == ResultCode.SUCCESS)
            {
              resultCode = ResultCode.CONSTRAINT_VIOLATION;
            }
            msgID = MSGID_SATUACM_DUPLICATE_CERT_ATTR;
            messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                                    certAttrName));
            break;
          }
          AttributeType userAttrType =
               DirectoryServer.getAttributeType(userAttrName, false);
          if (userAttrType == null)
          {
            if (resultCode == ResultCode.SUCCESS)
            {
              resultCode = ResultCode.NO_SUCH_ATTRIBUTE;
            }
            msgID = MSGID_SATUACM_NO_SUCH_ATTR;
            messages.add(getMessage(msgID, mapStr,
                                    String.valueOf(configEntryDN),
                                    userAttrName));
            break;
          }
          for (AttributeType attrType : newAttributeMap.values())
          {
            if (attrType.equals(userAttrType))
            {
              if (resultCode == ResultCode.SUCCESS)
              {
                resultCode = ResultCode.CONSTRAINT_VIOLATION;
              }
              msgID = MSGID_SATUACM_DUPLICATE_USER_ATTR;
              messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                                      attrType.getNameOrOID()));
              break mapLoop;
            }
          }
          newAttributeMap.put(certAttrName, userAttrType);
        }
      }
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "applyNewConfiguration", e);
      if (resultCode == ResultCode.SUCCESS)
      {
        resultCode = DirectoryServer.getServerErrorResultCode();
      }
      msgID = MSGID_SATUACM_CANNOT_GET_ATTR_MAP;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                              stackTraceToSingleLineString(e)));
    }
    // Get the set of base DNs below which to perform the searches.
    DN[] newBaseDNs = null;
    msgID = MSGID_SATUACM_DESCRIPTION_BASE_DN;
    DNConfigAttribute baseStub =
         new DNConfigAttribute(ATTR_CERTIFICATE_SUBJECT_BASEDN,
                               getMessage(msgID), false, true, false);
    try
    {
      DNConfigAttribute baseAttr =
           (DNConfigAttribute) configEntry.getConfigAttribute(baseStub);
      if (baseAttr != null)
      {
        List<DN> dnList = baseAttr.activeValues();
        newBaseDNs = new DN[dnList.size()];
        dnList.toArray(newBaseDNs);
      }
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "applyNewConfiguration", e);
      if (resultCode == ResultCode.SUCCESS)
      {
        resultCode = DirectoryServer.getServerErrorResultCode();
      }
      msgID = MSGID_SATUACM_CANNOT_GET_BASE_DN;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                              stackTraceToSingleLineString(e)));
    }
    if (resultCode == ResultCode.SUCCESS)
    {
      attributeMap = newAttributeMap;
      baseDNs      = newBaseDNs;
    }
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
  }
}
opends/src/server/org/opends/server/extensions/SubjectDNToUserAttributeCertificateMapper.java
New file
@@ -0,0 +1,584 @@
/*
 * 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.security.cert.Certificate;
import java.security.cert.X509Certificate;
import javax.security.auth.x500.X500Principal;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import org.opends.server.api.CertificateMapper;
import org.opends.server.api.ConfigurableComponent;
import org.opends.server.config.ConfigAttribute;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.config.DNConfigAttribute;
import org.opends.server.config.StringConfigAttribute;
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.DirectoryException;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.ConfigChangeResult;
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.config.ConfigConstants.*;
import static org.opends.server.loggers.Debug.*;
import static org.opends.server.messages.ExtensionsMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class implements a very simple Directory Server certificate mapper that
 * will map a certificate to a user only if that user's entry contains an
 * attribute with the subject of the client certificate.  There must be exactly
 * one matching user entry for the mapping to be successful.
 */
public class SubjectDNToUserAttributeCertificateMapper
       extends CertificateMapper
       implements ConfigurableComponent
{
  /**
   * The fully-qualified name of this class for debugging purposes.
   */
  private static final String CLASS_NAME =
       "org.opends.server.extensions.SubjectDNToUserAttributeCertificateMapper";
  // The attribute type that will be used to map the certificate's subject.
  private AttributeType subjectAttributeType;
  // The DN of the configuration entry for this certificate mapper.
  private DN configEntryDN;
  // The set of base DNs below which the search will be performed.
  private DN[] baseDNs;
  /**
   * Creates a new instance of this certificate mapper.  Note that all actual
   * initialization should be done in the
   * <CODE>initializeCertificateMapper</CODE> method.
   */
  public SubjectDNToUserAttributeCertificateMapper()
  {
    super();
    assert debugConstructor(CLASS_NAME);
  }
  /**
   * {@inheritDoc}
   */
  public void initializeCertificateMapper(ConfigEntry configEntry)
         throws ConfigException, InitializationException
  {
    assert debugEnter(CLASS_NAME, "initializeCertificateMapper",
                      String.valueOf(configEntry));
    this.configEntryDN = configEntry.getDN();
    // Get the attribute type that will be used to hold the certificate subject.
    int msgID = MSGID_SDTUACM_DESCRIPTION_SUBJECT_ATTR;
    StringConfigAttribute attrStub =
         new StringConfigAttribute(ATTR_CERTIFICATE_SUBJECT_ATTR,
                                   getMessage(msgID), true, false, false);
    try
    {
      StringConfigAttribute attrAttr =
           (StringConfigAttribute) configEntry.getConfigAttribute(attrStub);
      if (attrAttr == null)
      {
        msgID = MSGID_SDTUACM_NO_SUBJECT_ATTR;
        String message = getMessage(msgID, String.valueOf(configEntryDN),
                                    ATTR_CERTIFICATE_SUBJECT_ATTR);
        throw new ConfigException(msgID, message);
      }
      else
      {
        String attrName  = attrAttr.pendingValue();
        String lowerName = toLowerCase(attrName);
        subjectAttributeType =
             DirectoryServer.getAttributeType(lowerName, false);
        if (subjectAttributeType == null)
        {
          msgID = MSGID_SDTUACM_NO_SUCH_ATTR;
          String message = getMessage(msgID, String.valueOf(configEntryDN),
                                      attrName);
          throw new ConfigException(msgID, message);
        }
      }
    }
    catch (ConfigException ce)
    {
      throw ce;
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "initializeCertificateMapper", e);
      msgID = MSGID_SDTUACM_CANNOT_GET_SUBJECT_ATTR;
      String message = getMessage(msgID, String.valueOf(configEntryDN),
                                  stackTraceToSingleLineString(e));
      throw new InitializationException(msgID, message, e);
    }
    // Get the set of base DNs below which to perform the searches.
    baseDNs = null;
    msgID = MSGID_SDTUACM_DESCRIPTION_BASE_DN;
    DNConfigAttribute baseStub =
         new DNConfigAttribute(ATTR_CERTIFICATE_SUBJECT_BASEDN,
                               getMessage(msgID), false, true, false);
    try
    {
      DNConfigAttribute baseAttr =
           (DNConfigAttribute) configEntry.getConfigAttribute(baseStub);
      if (baseAttr != null)
      {
        List<DN> dnList = baseAttr.activeValues();
        baseDNs = new DN[dnList.size()];
        dnList.toArray(baseDNs);
      }
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "initializeCertificateMapper", e);
      msgID = MSGID_SDTUACM_CANNOT_GET_BASE_DN;
      String message = getMessage(msgID, String.valueOf(configEntryDN),
                                  stackTraceToSingleLineString(e));
      throw new InitializationException(msgID, message, e);
    }
    DirectoryServer.registerConfigurableComponent(this);
  }
  /**
   * {@inheritDoc}
   */
  public void finalizeCertificateMapper()
  {
    assert debugEnter(CLASS_NAME, "finalizeCertificateMapper");
    DirectoryServer.deregisterConfigurableComponent(this);
  }
  /**
   * {@inheritDoc}
   */
  public Entry mapCertificateToUser(Certificate[] certificateChain)
         throws DirectoryException
  {
    assert debugEnter(CLASS_NAME, "mapCertificateToUser",
                      String.valueOf(certificateChain));
    // Make sure that a peer certificate was provided.
    if ((certificateChain == null) || (certificateChain.length == 0))
    {
      int    msgID   = MSGID_SDTUACM_NO_PEER_CERTIFICATE;
      String message = getMessage(msgID);
      throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, message,
                                   msgID);
    }
    // Get the first certificate in the chain.  It must be an X.509 certificate.
    X509Certificate peerCertificate;
    try
    {
      peerCertificate = (X509Certificate) certificateChain[0];
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "mapCertificateToUser", e);
      int    msgID   = MSGID_SDTUACM_PEER_CERT_NOT_X509;
      String message =
           getMessage(msgID, String.valueOf(certificateChain[0].getType()));
      throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, message,
                                   msgID);
    }
    // Get the subject from the peer certificate and use it to create a search
    // filter.
    X500Principal peerPrincipal = peerCertificate.getSubjectX500Principal();
    String peerName = peerPrincipal.getName(X500Principal.RFC2253);
    AttributeValue value = new AttributeValue(subjectAttributeType, peerName);
    SearchFilter filter =
         SearchFilter.createEqualityFilter(subjectAttributeType, value);
    // If we have an explicit set of base DNs, then use it.  Otherwise, use the
    // set of public naming contexts in the server.
    DN[] bases = baseDNs;
    if (bases == null)
    {
      bases = new DN[0];
      bases = DirectoryServer.getPublicNamingContexts().keySet().toArray(bases);
    }
    // For each base DN, issue an internal search in an attempt to map the
    // certificate.
    Entry userEntry = null;
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    for (DN baseDN : bases)
    {
      InternalSearchOperation searchOperation =
           conn.processSearch(baseDN, SearchScope.WHOLE_SUBTREE, filter);
      for (SearchResultEntry entry : searchOperation.getSearchEntries())
      {
        if (userEntry == null)
        {
          userEntry = entry;
        }
        else
        {
          int    msgID   = MSGID_SDTUACM_MULTIPLE_MATCHING_ENTRIES;
          String message = getMessage(msgID, peerName,
                                      String.valueOf(userEntry.getDN()),
                                      String.valueOf(entry.getDN()));
          throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, message,
                                       msgID);
        }
      }
    }
    // If we've gotten here, then we either found exactly one user entry or we
    // didn't find any.  Either way, return the entry or null to the caller.
    return userEntry;
  }
  /**
   * Retrieves the DN of the configuration entry with which this
   * component is associated.
   *
   * @return  The DN of the configuration entry with which this
   *          component is associated.
   */
  public DN getConfigurableComponentEntryDN()
  {
    assert debugEnter(CLASS_NAME, "getConfigurableComponentEntryDN");
    return configEntryDN;
  }
  /**
   * Retrieves the set of configuration attributes that are associated
   * with this configurable component.
   *
   * @return  The set of configuration attributes that are associated
   *          with this configurable component.
   */
  public List<ConfigAttribute> getConfigurationAttributes()
  {
    assert debugEnter(CLASS_NAME, "getConfigurationAttributes");
    LinkedList<ConfigAttribute> attrList = new LinkedList<ConfigAttribute>();
    int msgID = MSGID_SDTUACM_DESCRIPTION_SUBJECT_ATTR;
    attrList.add(new StringConfigAttribute(ATTR_CERTIFICATE_SUBJECT_ATTR,
                          getMessage(msgID), true, false, false,
                          subjectAttributeType.getNameOrOID()));
    LinkedList<DN> dnList = new LinkedList<DN>();
    if (baseDNs != null)
    {
      for (DN baseDN : baseDNs)
      {
        dnList.add(baseDN);
      }
    }
    msgID = MSGID_SDTUACM_DESCRIPTION_BASE_DN;
    attrList.add(new DNConfigAttribute(ATTR_CERTIFICATE_SUBJECT_BASEDN,
                                       getMessage(msgID), false, true, false,
                                       dnList));
    return attrList;
  }
  /**
   * Indicates whether the provided configuration entry has an
   * acceptable configuration for this component.  If it does not,
   * then detailed information about the problem(s) should be added to
   * the provided list.
   *
   * @param  configEntry          The configuration entry for which to
   *                              make the determination.
   * @param  unacceptableReasons  A list that can be used to hold
   *                              messages about why the provided
   *                              entry does not have an acceptable
   *                              configuration.
   *
   * @return  <CODE>true</CODE> if the provided entry has an
   *          acceptable configuration for this component, or
   *          <CODE>false</CODE> if not.
   */
  public boolean hasAcceptableConfiguration(ConfigEntry configEntry,
                                            List<String> unacceptableReasons)
  {
    assert debugEnter(CLASS_NAME, "hasAcceptableConfiguration",
                      String.valueOf(configEntry), "java.util.List<String>");
    DN configEntryDN = configEntry.getDN();
    boolean configAcceptable = true;
    // Get the attribute type that will be used to hold the certificate subject.
    int msgID = MSGID_SDTUACM_DESCRIPTION_SUBJECT_ATTR;
    StringConfigAttribute attrStub =
         new StringConfigAttribute(ATTR_CERTIFICATE_SUBJECT_ATTR,
                                   getMessage(msgID), true, false, false);
    try
    {
      StringConfigAttribute attrAttr =
           (StringConfigAttribute) configEntry.getConfigAttribute(attrStub);
      if (attrAttr == null)
      {
        msgID = MSGID_SDTUACM_NO_SUBJECT_ATTR;
        String message = getMessage(msgID, String.valueOf(configEntryDN),
                                    ATTR_CERTIFICATE_SUBJECT_ATTR);
        unacceptableReasons.add(message);
        configAcceptable = false;
      }
      else
      {
        String attrName  = attrAttr.pendingValue();
        String lowerName = toLowerCase(attrName);
        AttributeType attrType =
             DirectoryServer.getAttributeType(lowerName, false);
        if (attrType == null)
        {
          msgID = MSGID_SDTUACM_NO_SUCH_ATTR;
          String message = getMessage(msgID, String.valueOf(configEntryDN),
                                      attrName);
          unacceptableReasons.add(message);
          configAcceptable = false;
        }
      }
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "hasAcceptableConfiguration", e);
      msgID = MSGID_SDTUACM_CANNOT_GET_SUBJECT_ATTR;
      String message = getMessage(msgID, String.valueOf(configEntryDN),
                                  stackTraceToSingleLineString(e));
      unacceptableReasons.add(message);
      configAcceptable = false;
    }
    // Get the set of base DNs below which to perform the searches.
    msgID = MSGID_SDTUACM_DESCRIPTION_BASE_DN;
    DNConfigAttribute baseStub =
         new DNConfigAttribute(ATTR_CERTIFICATE_SUBJECT_BASEDN,
                               getMessage(msgID), false, true, false);
    try
    {
      DNConfigAttribute baseAttr =
           (DNConfigAttribute) configEntry.getConfigAttribute(baseStub);
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "hasAcceptableConfiguration", e);
      msgID = MSGID_SDTUACM_CANNOT_GET_BASE_DN;
      String message = getMessage(msgID, String.valueOf(configEntryDN),
                                  stackTraceToSingleLineString(e));
      unacceptableReasons.add(message);
      configAcceptable = false;
    }
    return configAcceptable;
  }
  /**
   * Makes a best-effort attempt to apply the configuration contained
   * in the provided entry.  Information about the result of this
   * processing should be added to the provided message list.
   * Information should always be added to this list if a
   * configuration change could not be applied.  If detailed results
   * are requested, then information about the changes applied
   * successfully (and optionally about parameters that were not
   * changed) should also be included.
   *
   * @param  configEntry      The entry containing the new
   *                          configuration to apply for this
   *                          component.
   * @param  detailedResults  Indicates whether detailed information
   *                          about the processing should be added to
   *                          the list.
   *
   * @return  Information about the result of the configuration
   *          update.
   */
  public ConfigChangeResult applyNewConfiguration(ConfigEntry configEntry,
                                                  boolean detailedResults)
  {
    assert debugEnter(CLASS_NAME, "applyNewConfiguration",
                      String.valueOf(configEntry),
                      String.valueOf(detailedResults));
    DN                configEntryDN       = configEntry.getDN();
    ResultCode        resultCode          = ResultCode.SUCCESS;
    ArrayList<String> messages            = new ArrayList<String>();
    boolean           adminActionRequired = false;
    // Get the attribute type that will be used to hold the certificate subject.
    AttributeType newAttributeType = null;
    int msgID = MSGID_SDTUACM_DESCRIPTION_SUBJECT_ATTR;
    StringConfigAttribute attrStub =
         new StringConfigAttribute(ATTR_CERTIFICATE_SUBJECT_ATTR,
                                   getMessage(msgID), true, false, false);
    try
    {
      StringConfigAttribute attrAttr =
           (StringConfigAttribute) configEntry.getConfigAttribute(attrStub);
      if (attrAttr == null)
      {
        if (resultCode == ResultCode.SUCCESS)
        {
          resultCode = ResultCode.OBJECTCLASS_VIOLATION;
        }
        msgID = MSGID_SDTUACM_NO_SUBJECT_ATTR;
        messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                                ATTR_CERTIFICATE_SUBJECT_ATTR));
      }
      else
      {
        String attrName  = attrAttr.pendingValue();
        String lowerName = toLowerCase(attrName);
        newAttributeType = DirectoryServer.getAttributeType(lowerName, false);
        if (subjectAttributeType == null)
        {
          if (resultCode == ResultCode.SUCCESS)
          {
            resultCode = ResultCode.NO_SUCH_ATTRIBUTE;
          }
          msgID = MSGID_SDTUACM_NO_SUCH_ATTR;
          messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                                  attrName));
        }
      }
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "applyNewConfiguration", e);
      if (resultCode == ResultCode.SUCCESS)
      {
        resultCode = ResultCode.OBJECTCLASS_VIOLATION;
      }
      msgID = MSGID_SDTUACM_CANNOT_GET_SUBJECT_ATTR;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                              stackTraceToSingleLineString(e)));
    }
    // Get the set of base DNs below which to perform the searches.
    DN[] newBaseDNs = null;
    msgID = MSGID_SDTUACM_DESCRIPTION_BASE_DN;
    DNConfigAttribute baseStub =
         new DNConfigAttribute(ATTR_CERTIFICATE_SUBJECT_BASEDN,
                               getMessage(msgID), false, true, false);
    try
    {
      DNConfigAttribute baseAttr =
           (DNConfigAttribute) configEntry.getConfigAttribute(baseStub);
      if (baseAttr != null)
      {
        List<DN> dnList = baseAttr.activeValues();
        newBaseDNs = new DN[dnList.size()];
        dnList.toArray(newBaseDNs);
      }
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "applyNewConfiguration", e);
      if (resultCode == ResultCode.SUCCESS)
      {
        resultCode = ResultCode.OBJECTCLASS_VIOLATION;
      }
      msgID = MSGID_SDTUACM_CANNOT_GET_BASE_DN;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                              stackTraceToSingleLineString(e)));
    }
    if (resultCode == ResultCode.SUCCESS)
    {
      subjectAttributeType = newAttributeType;
      baseDNs              = newBaseDNs;
    }
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
  }
}
opends/src/server/org/opends/server/messages/ExtensionsMessages.java
@@ -4329,6 +4329,381 @@
  /**
   * The message ID for the message that will be used as the description for the
   * subject attribute type attribute.  It does not take any arguments.
   */
  public static final int MSGID_SDTUACM_DESCRIPTION_SUBJECT_ATTR =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 411;
  /**
   * The message ID for the message that will be used if the configuration entry
   * does not specify which attribute type should be used to hold certificate
   * subjects.  This takes two arguments, which are the DN of the configuration
   * entry and the attribute type that should be used to specify the subject
   * attribute.
   */
  public static final int MSGID_SDTUACM_NO_SUBJECT_ATTR =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 412;
  /**
   * The message ID for the message that will be used if subject attribute type
   * does not exist in the server schema.  This takes two arguments, which are
   * the DN of the configuration entry and the name of the specified attribute
   * type.
   */
  public static final int MSGID_SDTUACM_NO_SUCH_ATTR =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 413;
  /**
   * The message ID for the message that will be used if an error occurs while
   * trying to determine the subject attribute type.  This takes two arguments,
   * which are the DN of the configuration entry and a string representation of
   * the exception that was caught.
   */
  public static final int MSGID_SDTUACM_CANNOT_GET_SUBJECT_ATTR =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 414;
  /**
   * The message ID for the message that will be used as the description for the
   * search base DN attribute.  It does not take any arguments.
   */
  public static final int MSGID_SDTUACM_DESCRIPTION_BASE_DN =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 415;
  /**
   * The message ID for the message that will be used if an error occurs while
   * trying to determine the search base DN.  This takes two arguments,
   * which are the DN of the configuration entry and a string representation of
   * the exception that was caught.
   */
  public static final int MSGID_SDTUACM_CANNOT_GET_BASE_DN =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 416;
  /**
   * The message ID for the message that will be used if the client did not
   * present any certificate to the server.  This does not take any arguments.
   */
  public static final int MSGID_SDTUACM_NO_PEER_CERTIFICATE =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 417;
  /**
   * The message ID for the message that will be used if the client certificate
   * was not an X.509 certificate.  This takes a single argument, which is the
   * name of the certificate format.
   */
  public static final int MSGID_SDTUACM_PEER_CERT_NOT_X509 =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 418;
  /**
   * The message ID for the message that will be used if multiple user entries
   * matched the specified certificate subject.  This takes three arguments,
   * which are the certificate subject and the DNs of the first two users found
   * to match that subject.
   */
  public static final int MSGID_SDTUACM_MULTIPLE_MATCHING_ENTRIES =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 419;
  /**
   * The message ID for the message that will be used as the description for the
   * attribute map attribute.  It does not take any arguments.
   */
  public static final int MSGID_SATUACM_DESCRIPTION_ATTR_MAP =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 420;
  /**
   * The message ID for the message that will be used if the configuration entry
   * does not specify which attribute type should be used to map certificate
   * attributes to user attributes.  This takes two arguments, which are the DN
   * of the configuration entry and the attribute type that should be used to
   * specify the mapping.
   */
  public static final int MSGID_SATUACM_NO_MAP_ATTR =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 421;
  /**
   * The message ID for the message that will be used if an attribute map value
   * has an invalid format.  This takes two arguments, which are the DN of the
   * configuration entry and the invalid map value.
   */
  public static final int MSGID_SATUACM_INVALID_MAP_FORMAT =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 422;
  /**
   * The message ID for the message that will be used if there are multiple
   * mappings that target the same certificate attribute.  This takes two
   * arguments, which are the DN of the configuration entry and the name of the
   * certificate attribute.
   */
  public static final int MSGID_SATUACM_DUPLICATE_CERT_ATTR =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 423;
  /**
   * The message ID for the message that will be used if an attribute mapping
   * references a user attribute that is not defined in the server schema.
   * This takes two argumetns, which are the DN of the configuration entry and
   * the name of the undefined user attribute.
   */
  public static final int MSGID_SATUACM_NO_SUCH_ATTR =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 424;
  /**
   * The message ID for the message that will be used if there are multiple
   * mappings that target the same user attribute.  This takes two arguments,
   * which are the DN of the configuration entry and the name of the user
   * attribute.
   */
  public static final int MSGID_SATUACM_DUPLICATE_USER_ATTR =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 425;
  /**
   * The message ID for the message that will be used if an error occurs while
   * attempting to process the attribute mapping.  This takes two arguments,
   * which are the DN of the configuration entry and a string representation of
   * the exception that was caught.
   */
  public static final int MSGID_SATUACM_CANNOT_GET_ATTR_MAP =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 426;
  /**
   * The message ID for the message that will be used as the description for the
   * search base DN attribute.  It does not take any arguments.
   */
  public static final int MSGID_SATUACM_DESCRIPTION_BASE_DN =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 427;
  /**
   * The message ID for the message that will be used if an error occurs while
   * attempting to process the set of base DNs.  This takes two arguments,
   * which are the DN of the configuration entry and a string representation of
   * the exception that was caught.
   */
  public static final int MSGID_SATUACM_CANNOT_GET_BASE_DN =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 428;
  /**
   * The message ID for the message that will be used if the client did not
   * present any certificate to the server.  This does not take any arguments.
   */
  public static final int MSGID_SATUACM_NO_PEER_CERTIFICATE =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 429;
  /**
   * The message ID for the message that will be used if the client certificate
   * was not an X.509 certificate.  This takes a single argument, which is the
   * name of the certificate format.
   */
  public static final int MSGID_SATUACM_PEER_CERT_NOT_X509 =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 430;
  /**
   * The message ID for the message that will be used if the peer certificate
   * subject cannot be decoded as a DN.  This takes two arguments, which are
   * the peer certificate subject and a message explaining the problem that
   * occurred.
   */
  public static final int MSGID_SATUACM_CANNOT_DECODE_SUBJECT_AS_DN =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 431;
  /**
   * The message ID for the message that will be used if a peer certificate
   * subject does not contain any mappable attributes.  This takes a single
   * argument, which is the peer certificate subject.
   */
  public static final int MSGID_SATUACM_NO_MAPPABLE_ATTRIBUTES =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 432;
  /**
   * The message ID for the message that will be used if multiple user entries
   * matched the specified certificate subject.  This takes three arguments,
   * which are the certificate subject and the DNs of the first two users found
   * to match that subject.
   */
  public static final int MSGID_SATUACM_MULTIPLE_MATCHING_ENTRIES =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 433;
  /**
   * The message ID for the message that will be used as the description for the
   * fingerprint attribute type attribute.  It does not take any arguments.
   */
  public static final int MSGID_FCM_DESCRIPTION_FINGERPRINT_ATTR =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 434;
  /**
   * The message ID for the message that will be used if the configuration entry
   * does not specify which attribute type should be used to hold certificate
   * fingerprints.  This takes two arguments, which are the DN of the
   * configuration entry and the attribute type that should be used to specify
   * the fingerprint attribute.
   */
  public static final int MSGID_FCM_NO_FINGERPRINT_ATTR =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 435;
  /**
   * The message ID for the message that will be used if the fingerprint
   * attribute type does not exist in the server schema.  This takes two
   * arguments, which are the DN of the configuration entry and the name of the
   * specified attribute type.
   */
  public static final int MSGID_FCM_NO_SUCH_ATTR =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 436;
  /**
   * The message ID for the message that will be used if an error occurs while
   * trying to determine the fingerprint attribute type.  This takes two
   * arguments, which are the DN of the configuration entry and a string
   * representation of the exception that was caught.
   */
  public static final int MSGID_FCM_CANNOT_GET_FINGERPRINT_ATTR =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 437;
  /**
   * The message ID for the message that will be used as the description for the
   * fingerprint algorithm attribute.  It does not take any arguments.
   */
  public static final int MSGID_FCM_DESCRIPTION_FINGERPRINT_ALGORITHM =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 438;
  /**
   * The message ID for the message that will be used if the configuration entry
   * does not specify which digest algorithm should be used to compute
   * fingerprints.  This takes two arguments, which are the DN of the
   * configuration entry and the attribute type that should be used to specify
   * the fingerprint algorithm.
   */
  public static final int MSGID_FCM_NO_FINGERPRINT_ALGORITHM =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 439;
  /**
   * The message ID for the message that will be used if an error occurs while
   * trying to determine the fingerprint algorithm.  This takes two arguments,
   * which are the DN of the configuration entry and a string  representation of
   * the exception that was caught.
   */
  public static final int MSGID_FCM_CANNOT_GET_FINGERPRINT_ALGORITHM =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 440;
  /**
   * The message ID for the message that will be used as the description for the
   * search base DN attribute.  It does not take any arguments.
   */
  public static final int MSGID_FCM_DESCRIPTION_BASE_DN =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 441;
  /**
   * The message ID for the message that will be used if an error occurs while
   * trying to determine the search base DN.  This takes two arguments,
   * which are the DN of the configuration entry and a string representation of
   * the exception that was caught.
   */
  public static final int MSGID_FCM_CANNOT_GET_BASE_DN =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 442;
  /**
   * The message ID for the message that will be used if the client did not
   * present any certificate to the server.  This does not take any arguments.
   */
  public static final int MSGID_FCM_NO_PEER_CERTIFICATE =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 443;
  /**
   * The message ID for the message that will be used if the client certificate
   * was not an X.509 certificate.  This takes a single argument, which is the
   * name of the certificate format.
   */
  public static final int MSGID_FCM_PEER_CERT_NOT_X509 =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 444;
  /**
   * The message ID for the message that will be used if an error occurs while
   * attempting to compute the fingerprint for a certificate.  This takes two
   * arguments, which are the certificate subject and a string representation of
   * the exception that was caught.
   */
  public static final int MSGID_FCM_CANNOT_CALCULATE_FINGERPRINT =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 445;
  /**
   * The message ID for the message that will be used if multiple user entries
   * matched the specified certificate fingerprint.  This takes three arguments,
   * which are the certificate fingerprint and the DNs of the first two users
   * found to match that fingerprint.
   */
  public static final int MSGID_FCM_MULTIPLE_MATCHING_ENTRIES =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 446;
  /**
   * Associates a set of generic messages with the message IDs defined in this
   * class.
   */
@@ -6266,6 +6641,156 @@
                    "Cannot remove user %s as a member of static group %s " +
                    "because an error occurred while attempting to perform " +
                    "an internal modification to update the group:  %s.");
    registerMessage(MSGID_SDTUACM_DESCRIPTION_SUBJECT_ATTR,
                    "Specifies the name of the attribute type in user " +
                    "entries that contains the subjects of the certificates " +
                    "held by that user.  Changes to this configuration " +
                    "attribute will take effect immediately.");
    registerMessage(MSGID_SDTUACM_NO_SUBJECT_ATTR,
                    "Configuration entry %s does not contain required " +
                    "attribute %s, which is used to specify which attribute " +
                    "should contain the subjects of the certificates held " +
                    "by users.");
    registerMessage(MSGID_SDTUACM_NO_SUCH_ATTR,
                    "Configuration entry %s indicates that certificate " +
                    "subjects should be held in attribute %s, but this " +
                    "attribute is not defined in the server schema.");
    registerMessage(MSGID_SDTUACM_CANNOT_GET_SUBJECT_ATTR,
                    "An error occurred while attempting to determine which " +
                    "attribute type should be used to hold certificate " +
                    "subjects from configuration entry %s:  %s.");
    registerMessage(MSGID_SDTUACM_DESCRIPTION_BASE_DN,
                    "Specifies the base DNs below which the searches to " +
                    "find matching user entries will be performed.  If no " +
                    "base DN(s) are provided, then the server will search " +
                    "below all public naming contexts.  Changes to this " +
                    "configuration attribute will take effect immediately.");
    registerMessage(MSGID_SDTUACM_CANNOT_GET_BASE_DN,
                    "An error occurred while attempting to determine the " +
                    "search base DN(s) from configuration entry %s:  %s.");
    registerMessage(MSGID_SDTUACM_NO_PEER_CERTIFICATE,
                    "Could not map the provided certificate chain to a user " +
                    "entry because no peer certificate was available.");
    registerMessage(MSGID_SDTUACM_PEER_CERT_NOT_X509,
                    "Could not map the provided certificate chain to a user " +
                    "because the peer certificate was not an X.509 " +
                    "certificate (peer certificate format was %s).");
    registerMessage(MSGID_SDTUACM_MULTIPLE_MATCHING_ENTRIES,
                    "The certificate with subject %s could not be mapped to " +
                    "exactly one user.  It maps to both %s and %s.");
    registerMessage(MSGID_SATUACM_DESCRIPTION_ATTR_MAP,
                    "Specifies the name of the attribute type in user " +
                    "entries that defines the mapping between attributes " +
                    "in certificate subjects and attributes in user " +
                    "entries.  Values should be in the form " +
                    "'certattr:userattr'.  Changes to this configuration " +
                    "attribute will take effect immediately.");
    registerMessage(MSGID_SATUACM_NO_MAP_ATTR,
                    "Configuration entry %s does not contain required " +
                    "attribute %s, which is used to specify the mappings " +
                    "between attributes in certificate subjects and " +
                    "attributes in user entries.");
    registerMessage(MSGID_SATUACM_INVALID_MAP_FORMAT,
                    "Configuration entry %s has value '%s' which violates " +
                    "the format required for attribute mappings.  The " +
                    "expected format is 'certattr:userattr'.");
    registerMessage(MSGID_SATUACM_DUPLICATE_CERT_ATTR,
                    "Configuration entry %s contains multiple mappings " +
                    "for certificate attribute %s.");
    registerMessage(MSGID_SATUACM_NO_SUCH_ATTR,
                    "Mapping %s in configuration entry %s references " +
                    "attribute %s which is not defined in the server schema.");
    registerMessage(MSGID_SATUACM_DUPLICATE_USER_ATTR,
                    "Configuration entry %s contains multiple mappings " +
                    "for user attribute %s.");
    registerMessage(MSGID_SATUACM_CANNOT_GET_ATTR_MAP,
                    "An error occurred while attempting to determine the set " +
                    "of attribute mappings from configuration entry %s:  %s.");
    registerMessage(MSGID_SATUACM_DESCRIPTION_BASE_DN,
                    "Specifies the base DNs below which the searches to " +
                    "find matching user entries will be performed.  If no " +
                    "base DN(s) are provided, then the server will search " +
                    "below all public naming contexts.  Changes to this " +
                    "configuration attribute will take effect immediately.");
    registerMessage(MSGID_SATUACM_CANNOT_GET_BASE_DN,
                    "An error occurred while attempting to determine the " +
                    "search base DN(s) from configuration entry %s:  %s.");
    registerMessage(MSGID_SATUACM_NO_PEER_CERTIFICATE,
                    "Could not map the provided certificate chain to a user " +
                    "entry because no peer certificate was available.");
    registerMessage(MSGID_SATUACM_PEER_CERT_NOT_X509,
                    "Could not map the provided certificate chain to a user " +
                    "because the peer certificate was not an X.509 " +
                    "certificate (peer certificate format was %s).");
    registerMessage(MSGID_SATUACM_CANNOT_DECODE_SUBJECT_AS_DN,
                    "Unable to decode peer certificate subject %s as a DN:  " +
                    "%s.");
    registerMessage(MSGID_SATUACM_NO_MAPPABLE_ATTRIBUTES,
                    "Peer certificate subject %s does not contain any " +
                    "attributes for which a mapping has been established.");
    registerMessage(MSGID_SATUACM_MULTIPLE_MATCHING_ENTRIES,
                    "The certificate with subject %s could not be mapped to " +
                    "exactly one user.  It maps to both %s and %s.");
    registerMessage(MSGID_FCM_DESCRIPTION_FINGERPRINT_ATTR,
                    "Specifies the name of the attribute type in user " +
                    "entries that contains the fingerprints of the " +
                    "certificates held by that user.  Changes to this " +
                    "configuration attribute will take effect immediately.");
    registerMessage(MSGID_FCM_NO_FINGERPRINT_ATTR,
                    "Configuration entry %s does not contain required " +
                    "attribute %s, which is used to specify which attribute " +
                    "should contain the fingerprints of the certificates " +
                    "held by users.");
    registerMessage(MSGID_FCM_NO_SUCH_ATTR,
                    "Configuration entry %s indicates that certificate " +
                    "fingerprints should be held in attribute %s, but this " +
                    "attribute is not defined in the server schema.");
    registerMessage(MSGID_FCM_CANNOT_GET_FINGERPRINT_ATTR,
                    "An error occurred while attempting to determine which " +
                    "attribute type should be used to hold certificate " +
                    "fingerprints from configuration entry %s:  %s.");
    registerMessage(MSGID_FCM_DESCRIPTION_FINGERPRINT_ALGORITHM,
                    "Specifies the name of the digest algorithm used for " +
                    "the certificate fingerprints.  The value should be " +
                    "either 'MD5' or 'SHA1'.  Changes to this configuration " +
                    "attribute will take effect immediately.");
    registerMessage(MSGID_FCM_NO_FINGERPRINT_ALGORITHM,
                    "Configuration entry %s does not contain required " +
                    "attribute %s, which is used to specify which digest " +
                    "algorithm should be used to compute certificate " +
                    "fingerprints.");
    registerMessage(MSGID_FCM_CANNOT_GET_FINGERPRINT_ALGORITHM,
                    "An error occurred while attempting to determine the " +
                    "digest algorithm from configuration entry %s:  %s.");
    registerMessage(MSGID_FCM_DESCRIPTION_BASE_DN,
                    "Specifies the base DNs below which the searches to " +
                    "find matching user entries will be performed.  If no " +
                    "base DN(s) are provided, then the server will search " +
                    "below all public naming contexts.  Changes to this " +
                    "configuration attribute will take effect immediately.");
    registerMessage(MSGID_FCM_CANNOT_GET_BASE_DN,
                    "An error occurred while attempting to determine the " +
                    "search base DN(s) from configuration entry %s:  %s.");
    registerMessage(MSGID_FCM_NO_PEER_CERTIFICATE,
                    "Could not map the provided certificate chain to a user " +
                    "entry because no peer certificate was available.");
    registerMessage(MSGID_FCM_PEER_CERT_NOT_X509,
                    "Could not map the provided certificate chain to a user " +
                    "because the peer certificate was not an X.509 " +
                    "certificate (peer certificate format was %s).");
    registerMessage(MSGID_FCM_CANNOT_CALCULATE_FINGERPRINT,
                    "An error occurred while attempting to calculate the " +
                    "fingerprint for the peer certificate with subject %s:  " +
                    "%s.");
    registerMessage(MSGID_FCM_MULTIPLE_MATCHING_ENTRIES,
                    "The certificate with fingerprint %s could not be mapped " +
                    "to exactly one user.  It maps to both %s and %s.");
  }
}
opends/src/server/org/opends/server/util/StaticUtils.java
@@ -759,6 +759,37 @@
  /**
   * Retrieves a string representation of the contents of the provided byte
   * array using hexadecimal characters and a colon between each byte.
   *
   * @param  b  The byte array containing the data.
   *
   * @return  A string representation of the contents of the provided byte
   *          array using hexadecimal characters.
   */
  public static String bytesToColonDelimitedHex(byte[] b)
  {
    if ((b == null) || (b.length == 0))
    {
      return "";
    }
    int arrayLength = b.length;
    StringBuilder buffer = new StringBuilder((arrayLength - 1) * 3 + 2);
    buffer.append(byteToHex(b[0]));
    for (int i=1; i < arrayLength; i++)
    {
      buffer.append(":");
      buffer.append(byteToHex(b[i]));
    }
    return buffer.toString();
  }
  /**
   * Retrieves a string representation of the contents of the provided byte
   * buffer using hexadecimal characters and a space between each byte.
   *
   * @param  b  The byte buffer containing the data.
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/FingerprintCertificateMapperTestCase.java
New file
@@ -0,0 +1,681 @@
/*
 * 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.io.File;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.opends.server.TestCaseUtils;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ModifyOperation;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.tools.LDAPSearch;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import org.opends.server.types.Modification;
import org.opends.server.types.ModificationType;
import org.opends.server.types.ResultCode;
import static org.testng.Assert.*;
/**
 * A set of test cases for the fingerprint certificate mapper.
 */
public class FingerprintCertificateMapperTestCase
       extends ExtensionsTestCase
{
  /**
   * Ensures that the Directory Server is running.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @BeforeClass()
  public void startServer()
         throws Exception
  {
    TestCaseUtils.startServer();
  }
  /**
   * Retrieves a set of invalid configurations that cannot be used to
   * initialize the certificate mapper.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @DataProvider(name = "invalidConfigs")
  public Object[][] getInvalidConfigurations()
         throws Exception
  {
    List<Entry> entries = TestCaseUtils.makeEntries(
      "dn: cn=No Fingerprint Attr,cn=Certificate Mappers,cn=config",
      "objectClass: top",
      "objectClass: ds-cfg-certificate-mapper",
      "objectClass: ds-cfg-fingerprint-certificate-mapper",
      "cn: No Fingerprint Attr",
      "ds-cfg-certificate-mapper-class: org.opends.server.extensions." +
           "FingerprintCertificateMapper",
      "ds-cfg-certificate-mapper-enabled: true",
      "ds-cfg-certificate-fingerprint-algorithm: MD5",
      "",
      "dn: cn=Undefined Fingerprint Attr,cn=Certificate Mappers,cn=config",
      "objectClass: top",
      "objectClass: ds-cfg-certificate-mapper",
      "objectClass: ds-cfg-fingerprint-certificate-mapper",
      "cn: Undefined Fingerprint Attr",
      "ds-cfg-certificate-mapper-class: org.opends.server.extensions." +
           "FingerprintCertificateMapper",
      "ds-cfg-certificate-mapper-enabled: true",
      "ds-cfg-certificate-fingerprint-attribute-type: undefined",
      "ds-cfg-certificate-fingerprint-algorithm: MD5",
      "",
      "dn: cn=No Fingerprint Algorithm,cn=Certificate Mappers,cn=config",
      "objectClass: top",
      "objectClass: ds-cfg-certificate-mapper",
      "objectClass: ds-cfg-fingerprint-certificate-mapper",
      "cn: No Fingerprint Algorithm",
      "ds-cfg-certificate-mapper-class: org.opends.server.extensions." +
           "FingerprintCertificateMapper",
      "ds-cfg-certificate-mapper-enabled: true",
      "ds-cfg-certificate-fingerprint-attribute-type: " +
           "ds-certificate-fingerprint",
      "",
      "dn: cn=Invalid Fingerprint Algorithm,cn=Certificate Mappers,cn=config",
      "objectClass: top",
      "objectClass: ds-cfg-certificate-mapper",
      "objectClass: ds-cfg-fingerprint-certificate-mapper",
      "cn: Invalid Fingerprint Algorithm",
      "ds-cfg-certificate-mapper-class: org.opends.server.extensions." +
           "FingerprintCertificateMapper",
      "ds-cfg-certificate-mapper-enabled: true",
      "ds-cfg-certificate-fingerprint-attribute-type: " +
           "ds-certificate-fingerprint",
      "ds-cfg-certificate-fingerprint-algorithm: invalid",
      "",
      "dn: cn=Invalid Base DN,cn=Certificate Mappers,cn=config",
      "objectClass: top",
      "objectClass: ds-cfg-certificate-mapper",
      "objectClass: ds-cfg-fingerprint-certificate-mapper",
      "cn: Invalid Base DN",
      "ds-cfg-certificate-mapper-class: org.opends.server.extensions." +
           "FingerprintCertificateMapper",
      "ds-cfg-certificate-mapper-enabled: true",
      "ds-cfg-certificate-fingerprint-attribute-type: " +
           "ds-certificate-fingerprint",
      "ds-cfg-certificate-fingerprint-algorithm: MD5",
      "ds-cfg-certificate-user-base-dn: invalid");
    Object[][] configEntries = new Object[entries.size()][1];
    for (int i=0; i < configEntries.length; i++)
    {
      configEntries[i] = new Object[] { entries.get(i) };
    }
    return configEntries;
  }
  /**
   * Tests initialization with an invalid configuration.
   *
   * @param  e  The configuration entry to use to initialize the certificate
   *            mapper.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "invalidConfigs",
        expectedExceptions = { ConfigException.class,
                               InitializationException.class })
  public void testInvalidConfigs(Entry e)
         throws Exception
  {
    DN parentDN = DN.decode("cn=Certificate Mappers,cn=config");
    ConfigEntry parentEntry = DirectoryServer.getConfigEntry(parentDN);
    ConfigEntry configEntry = new ConfigEntry(e, parentEntry);
    FingerprintCertificateMapper mapper = new FingerprintCertificateMapper();
    mapper.initializeCertificateMapper(configEntry);
  }
  /**
   * Tests a successful mapping using the default configuration.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testSuccessfulMappingDefaultConfig()
         throws Exception
  {
    enableMapper();
    try
    {
      TestCaseUtils.initializeTestBackend(true);
      TestCaseUtils.addEntry(
        "dn: uid=test.user,o=test",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "objectClass: ds-certificate-user",
        "uid: test.user",
        "givenName: Test",
        "sn: User",
        "cn: Test User",
        "ds-certificate-fingerprint: " +
             "07:5A:AB:4B:E1:DD:E3:05:83:C0:FE:5F:A3:E8:1E:EB");
      String keyStorePath = DirectoryServer.getServerRoot() + File.separator +
                            "config" + File.separator + "client.keystore";
      String trustStorePath = DirectoryServer.getServerRoot() + File.separator +
                              "config" + File.separator + "client.truststore";
      String[] args =
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapsPort()),
        "-Z",
        "-K", keyStorePath,
        "-W", "password",
        "-P", trustStorePath,
        "-r",
        "-b", "",
        "-s", "base",
        "(objectClass=*)"
      };
      assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
    }
    finally
    {
      disableMapper();
    }
  }
  /**
   * Tests a successful mapping using the SHA-1 digest algorithm..
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testSuccessfulMappingSHA1()
         throws Exception
  {
    enableMapper();
    try
    {
      setFingerprintAlgorithm("SHA1");
      TestCaseUtils.initializeTestBackend(true);
      TestCaseUtils.addEntry(
        "dn: uid=test.user,o=test",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "objectClass: ds-certificate-user",
        "uid: test.user",
        "givenName: Test",
        "sn: User",
        "cn: Test User",
        "ds-certificate-fingerprint: " +
             "CB:A4:C7:A0:46:1F:44:88:12:23:56:49:F9:54:F4:37:E1:9F:9F:A4");
      String keyStorePath = DirectoryServer.getServerRoot() + File.separator +
                            "config" + File.separator + "client.keystore";
      String trustStorePath = DirectoryServer.getServerRoot() + File.separator +
                              "config" + File.separator + "client.truststore";
      String[] args =
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapsPort()),
        "-Z",
        "-K", keyStorePath,
        "-W", "password",
        "-P", trustStorePath,
        "-r",
        "-b", "",
        "-s", "base",
        "(objectClass=*)"
      };
      assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
    }
    finally
    {
      disableMapper();
      setFingerprintAlgorithm("MD5");
    }
  }
  /**
   * Tests a failed mapping due to no matching entries.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testFailedMappingNoMatchingEntries()
         throws Exception
  {
    enableMapper();
    try
    {
      TestCaseUtils.initializeTestBackend(true);
      TestCaseUtils.addEntry(
        "dn: uid=test.user,o=test",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "objectClass: ds-certificate-user",
        "uid: test.user",
        "givenName: Test",
        "sn: User",
        "cn: Test User");
      String keyStorePath = DirectoryServer.getServerRoot() + File.separator +
                            "config" + File.separator + "client.keystore";
      String trustStorePath = DirectoryServer.getServerRoot() + File.separator +
                              "config" + File.separator + "client.truststore";
      String[] args =
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapsPort()),
        "-Z",
        "-K", keyStorePath,
        "-W", "password",
        "-P", trustStorePath,
        "-r",
        "-b", "",
        "-s", "base",
        "(objectClass=*)"
      };
      assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
    }
    finally
    {
      disableMapper();
    }
  }
  /**
   * Tests a failed mapping due to multiple matching entries.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testFailedMappingMultipleMatchingEntries()
         throws Exception
  {
    enableMapper();
    try
    {
      TestCaseUtils.initializeTestBackend(true);
      TestCaseUtils.addEntries(
        "dn: uid=test.user1,o=test",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "objectClass: ds-certificate-user",
        "uid: test.user1",
        "givenName: Test",
        "sn: User",
        "cn: Test User 1",
        "ds-certificate-fingerprint: " +
             "07:5A:AB:4B:E1:DD:E3:05:83:C0:FE:5F:A3:E8:1E:EB",
        "",
        "dn: uid=test.user2,o=test",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "objectClass: ds-certificate-user",
        "uid: test.user2",
        "givenName: Test",
        "sn: User",
        "cn: Test User 2",
        "ds-certificate-fingerprint: " +
             "07:5A:AB:4B:E1:DD:E3:05:83:C0:FE:5F:A3:E8:1E:EB");
      String keyStorePath = DirectoryServer.getServerRoot() + File.separator +
                            "config" + File.separator + "client.keystore";
      String trustStorePath = DirectoryServer.getServerRoot() + File.separator +
                              "config" + File.separator + "client.truststore";
      String[] args =
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapsPort()),
        "-Z",
        "-K", keyStorePath,
        "-W", "password",
        "-P", trustStorePath,
        "-r",
        "-b", "",
        "-s", "base",
        "(objectClass=*)"
      };
      assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
    }
    finally
    {
      disableMapper();
    }
  }
  /**
   * Tests to ensure that an attmept to remove the fingerprint attribute will
   * fail.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testRemoveFingerprintAttribute()
         throws Exception
  {
    String mapperDN = "cn=Fingerprint Mapper,cn=Certificate Mappers,cn=config";
    Attribute a =
         new Attribute(DirectoryServer.getAttributeType(
                            "ds-cfg-certificate-fingerprint-attribute-type"));
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.DELETE, a));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         conn.processModify(DN.decode(mapperDN), mods);
    assertFalse(modifyOperation.getResultCode() == ResultCode.SUCCESS);
  }
  /**
   * Tests to ensure that an attmept to remove the fingerprint algorithm will
   * fail.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testRemoveFingerprintAlgorithm()
         throws Exception
  {
    String mapperDN = "cn=Fingerprint Mapper,cn=Certificate Mappers,cn=config";
    Attribute a =
         new Attribute(DirectoryServer.getAttributeType(
                            "ds-cfg-certificate-fingerprint-algorithm"));
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.DELETE, a));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         conn.processModify(DN.decode(mapperDN), mods);
    assertFalse(modifyOperation.getResultCode() == ResultCode.SUCCESS);
  }
  /**
   * Tests to ensure that an attmept to set an undefined fingerprint attribute
   * will fail.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { AssertionError.class })
  public void testSetUndefinedFingerprintAttribute()
         throws Exception
  {
    setFingerprintAttribute("undefined");
  }
  /**
   * Tests to ensure that an attmept to set an undefined fingerprint algorithm
   * will fail.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { AssertionError.class })
  public void testSetUndefinedFingerprintAlgorithm()
         throws Exception
  {
    setFingerprintAlgorithm("undefined");
  }
  /**
   * Tests to ensure that an attmept to set an invalid base DN will fail.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { AssertionError.class })
  public void testSetInvalidBaseDN()
         throws Exception
  {
    setBaseDNs(new String[] { "invalid" });
  }
  /**
   * Alters the configuration of the SASL EXTERNAL mechanism handler so that it
   * uses the Subject DN to User Attribute certificate mapper.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  private void enableMapper()
          throws Exception
  {
    String externalDN = "cn=EXTERNAL,cn=SASL Mechanisms,cn=config";
    String mapperDN = "cn=Fingerprint Mapper,cn=Certificate Mappers,cn=config";
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                              new Attribute("ds-cfg-certificate-mapper-dn",
                                            mapperDN)));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         conn.processModify(DN.decode(externalDN), mods);
    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Alters the configuration of the SASL EXTERNAL mechanism handler so that it
   * uses the Subject Equals DN certificate mapper.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  private void disableMapper()
          throws Exception
  {
    String externalDN = "cn=EXTERNAL,cn=SASL Mechanisms,cn=config";
    String mapperDN = "cn=Subject Equals DN,cn=Certificate Mappers,cn=config";
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                              new Attribute("ds-cfg-certificate-mapper-dn",
                                            mapperDN)));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         conn.processModify(DN.decode(externalDN), mods);
    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Alters the configuration of the fingerprint certificate mapper so that it
   * will look for the fingerprint in the specified attribute.
   *
   * @param  attrName  The name of the attribute in which to look for the
   *                   certificate subject.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  private void setFingerprintAttribute(String attrName)
          throws Exception
  {
    String mapperDN = "cn=Fingerprint Mapper,cn=Certificate Mappers,cn=config";
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
         new Attribute("ds-cfg-certificate-fingerprint-attribute-type",
                       attrName)));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         conn.processModify(DN.decode(mapperDN), mods);
    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Alters the configuration of the fingerprint certificate mapper so that it
   * will use the specified fingerprint algorithm.
   *
   * @param  algorithm  The name of the fingerprint algorithm to use.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  private void setFingerprintAlgorithm(String algorithm)
          throws Exception
  {
    String mapperDN = "cn=Fingerprint Mapper,cn=Certificate Mappers,cn=config";
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                      new Attribute("ds-cfg-certificate-fingerprint-algorithm",
                                    algorithm)));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         conn.processModify(DN.decode(mapperDN), mods);
    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Alters the configuration of the Subject DN to User Attribute certificate
   * mapper so that it will look for the subject DN below the specified set of
   * base DNs.
   *
   * @param  baseDNs  The set of base DNs to use when mapping certificates to
   *                  users.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  private void setBaseDNs(String[] baseDNs)
          throws Exception
  {
    String mapperDN = "cn=Fingerprint Mapper,cn=Certificate Mappers,cn=config";
    AttributeType attrType =
         DirectoryServer.getAttributeType("ds-cfg-certificate-user-base-dn");
    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>();
    if (baseDNs != null)
    {
      for (String baseDN : baseDNs)
      {
        values.add(new AttributeValue(attrType, baseDN));
      }
    }
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                              new Attribute(attrType, attrType.getNameOrOID(),
                                            values)));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         conn.processModify(DN.decode(mapperDN), mods);
    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SubjectAttributeToUserAttributeCertificateMapperTestCase.java
New file
@@ -0,0 +1,851 @@
/*
 * 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.io.File;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.opends.server.TestCaseUtils;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ModifyOperation;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.tools.LDAPSearch;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import org.opends.server.types.Modification;
import org.opends.server.types.ModificationType;
import org.opends.server.types.ResultCode;
import static org.testng.Assert.*;
/**
 * A set of test cases for the Subject Attribute to User Attribute certificate
 * mapper.
 */
public class SubjectAttributeToUserAttributeCertificateMapperTestCase
       extends ExtensionsTestCase
{
  /**
   * Ensures that the Directory Server is running.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @BeforeClass()
  public void startServer()
         throws Exception
  {
    TestCaseUtils.startServer();
  }
  /**
   * Retrieves a set of invalid configurations that cannot be used to
   * initialize the certificate mapper.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @DataProvider(name = "invalidConfigs")
  public Object[][] getInvalidConfigurations()
         throws Exception
  {
    List<Entry> entries = TestCaseUtils.makeEntries(
      "dn: cn=No Map Attr,cn=Certificate Mappers,cn=config",
      "objectClass: top",
      "objectClass: ds-cfg-certificate-mapper",
      "objectClass: " +
           "ds-cfg-subject-attribute-to-user-attribute-certificate-mapper",
      "cn: No Map Attr",
      "ds-cfg-certificate-mapper-class: org.opends.server.extensions." +
           "SubjectAttributeToUserAttributeCertificateMapper",
      "ds-cfg-certificate-mapper-enabled: true",
      "",
      "dn: cn=No Map Colon,cn=Certificate Mappers,cn=config",
      "objectClass: top",
      "objectClass: ds-cfg-certificate-mapper",
      "objectClass: " +
           "ds-cfg-subject-attribute-to-user-attribute-certificate-mapper",
      "cn: No Map Colon",
      "ds-cfg-certificate-mapper-class: org.opends.server.extensions." +
           "SubjectAttributeToUserAttributeCertificateMapper",
      "ds-cfg-certificate-mapper-enabled: true",
      "ds-cfg-certificate-subject-attribute-mapping: nomapcolon",
      "",
      "dn: cn=No Map Cert Attr,cn=Certificate Mappers,cn=config",
      "objectClass: top",
      "objectClass: ds-cfg-certificate-mapper",
      "objectClass: " +
           "ds-cfg-subject-attribute-to-user-attribute-certificate-mapper",
      "cn: No Map Cert Attr",
      "ds-cfg-certificate-mapper-class: org.opends.server.extensions." +
           "SubjectAttributeToUserAttributeCertificateMapper",
      "ds-cfg-certificate-mapper-enabled: true",
      "ds-cfg-certificate-subject-attribute-mapping: :cn",
      "",
      "dn: cn=No Map User Attr,cn=Certificate Mappers,cn=config",
      "objectClass: top",
      "objectClass: ds-cfg-certificate-mapper",
      "objectClass: " +
           "ds-cfg-subject-attribute-to-user-attribute-certificate-mapper",
      "cn: No Map User Attr",
      "ds-cfg-certificate-mapper-class: org.opends.server.extensions." +
           "SubjectAttributeToUserAttributeCertificateMapper",
      "ds-cfg-certificate-mapper-enabled: true",
      "ds-cfg-certificate-subject-attribute-mapping: cn:",
      "",
      "dn: cn=Undefined User Attr,cn=Certificate Mappers,cn=config",
      "objectClass: top",
      "objectClass: ds-cfg-certificate-mapper",
      "objectClass: " +
           "ds-cfg-subject-attribute-to-user-attribute-certificate-mapper",
      "cn: Undefined User Attr",
      "ds-cfg-certificate-mapper-class: org.opends.server.extensions." +
           "SubjectAttributeToUserAttributeCertificateMapper",
      "ds-cfg-certificate-mapper-enabled: true",
      "ds-cfg-certificate-subject-attribute-mapping: cn:undefined",
      "",
      "dn: cn=Duplicate Cert Attr,cn=Certificate Mappers,cn=config",
      "objectClass: top",
      "objectClass: ds-cfg-certificate-mapper",
      "objectClass: " +
           "ds-cfg-subject-attribute-to-user-attribute-certificate-mapper",
      "cn: Duplicate Cert Attr",
      "ds-cfg-certificate-mapper-class: org.opends.server.extensions." +
           "SubjectAttributeToUserAttributeCertificateMapper",
      "ds-cfg-certificate-mapper-enabled: true",
      "ds-cfg-certificate-subject-attribute-mapping: cn:cn",
      "ds-cfg-certificate-subject-attribute-mapping: cn:sn",
      "",
      "dn: cn=Duplicate User Attr,cn=Certificate Mappers,cn=config",
      "objectClass: top",
      "objectClass: ds-cfg-certificate-mapper",
      "objectClass: " +
           "ds-cfg-subject-attribute-to-user-attribute-certificate-mapper",
      "cn: Duplicate User Attr",
      "ds-cfg-certificate-mapper-class: org.opends.server.extensions." +
           "SubjectAttributeToUserAttributeCertificateMapper",
      "ds-cfg-certificate-mapper-enabled: true",
      "ds-cfg-certificate-subject-attribute-mapping: cn:cn",
      "ds-cfg-certificate-subject-attribute-mapping: e:cn",
      "",
      "dn: cn=Invalid Base DN,cn=Certificate Mappers,cn=config",
      "objectClass: top",
      "objectClass: ds-cfg-certificate-mapper",
      "objectClass: " +
           "ds-cfg-subject-attribute-to-user-attribute-certificate-mapper",
      "cn: Invalid Base DN",
      "ds-cfg-certificate-mapper-class: org.opends.server.extensions." +
           "SubjectAttributeToUserAttributeCertificateMapper",
      "ds-cfg-certificate-mapper-enabled: true",
      "ds-cfg-certificate-subject-attribute-mapping: cn:cn",
      "ds-cfg-certificate-user-base-dn: invalid");
    Object[][] configEntries = new Object[entries.size()][1];
    for (int i=0; i < configEntries.length; i++)
    {
      configEntries[i] = new Object[] { entries.get(i) };
    }
    return configEntries;
  }
  /**
   * Tests initialization with an invalid configuration.
   *
   * @param  e  The configuration entry to use to initialize the certificate
   *            mapper.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "invalidConfigs",
        expectedExceptions = { ConfigException.class,
                               InitializationException.class })
  public void testInvalidConfigs(Entry e)
         throws Exception
  {
    DN parentDN = DN.decode("cn=Certificate Mappers,cn=config");
    ConfigEntry parentEntry = DirectoryServer.getConfigEntry(parentDN);
    ConfigEntry configEntry = new ConfigEntry(e, parentEntry);
    SubjectAttributeToUserAttributeCertificateMapper mapper =
         new SubjectAttributeToUserAttributeCertificateMapper();
    mapper.initializeCertificateMapper(configEntry);
  }
  /**
   * Tests a successful mapping using the default configuration.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testSuccessfulMappingDefaultConfig()
         throws Exception
  {
    enableMapper();
    try
    {
      TestCaseUtils.initializeTestBackend(true);
      TestCaseUtils.addEntry(
        "dn: uid=test.user,o=test",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "objectClass: ds-certificate-user",
        "uid: test.user",
        "givenName: Test",
        "sn: User",
        "cn: Test User");
      String keyStorePath = DirectoryServer.getServerRoot() + File.separator +
                            "config" + File.separator + "client.keystore";
      String trustStorePath = DirectoryServer.getServerRoot() + File.separator +
                              "config" + File.separator + "client.truststore";
      String[] args =
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapsPort()),
        "-Z",
        "-K", keyStorePath,
        "-W", "password",
        "-P", trustStorePath,
        "-r",
        "-b", "",
        "-s", "base",
        "(objectClass=*)"
      };
      assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
    }
    finally
    {
      disableMapper();
    }
  }
  /**
   * Tests a successful mapping with multiple attributes.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testSuccessfulMappingMultipleAttributes()
         throws Exception
  {
    enableMapper();
    try
    {
      setAttributeMappings(new String[] { "cn:cn", "o:o" });
      TestCaseUtils.initializeTestBackend(true);
      TestCaseUtils.addEntry(
        "dn: uid=test.user,o=test",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "objectClass: ds-certificate-user",
        "uid: test.user",
        "givenName: Test",
        "sn: User",
        "cn: Test User",
        "o: test");
      String keyStorePath = DirectoryServer.getServerRoot() + File.separator +
                            "config" + File.separator + "client.keystore";
      String trustStorePath = DirectoryServer.getServerRoot() + File.separator +
                              "config" + File.separator + "client.truststore";
      String[] args =
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapsPort()),
        "-Z",
        "-K", keyStorePath,
        "-W", "password",
        "-P", trustStorePath,
        "-r",
        "-b", "",
        "-s", "base",
        "(objectClass=*)"
      };
      assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
    }
    finally
    {
      disableMapper();
      setAttributeMappings(new String[] { "cn:cn", "e:mail" });
    }
  }
  /**
   * Tests a failed mapping due to no mappable attributes in the certificate.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testFailedNoMappableAttributes()
         throws Exception
  {
    enableMapper();
    try
    {
      setAttributeMappings(new String[] { "e:mail" });
      TestCaseUtils.initializeTestBackend(true);
      TestCaseUtils.addEntry(
        "dn: uid=test.user,o=test",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "objectClass: ds-certificate-user",
        "uid: test.user",
        "givenName: Test",
        "sn: User",
        "cn: Test User",
        "o: test");
      String keyStorePath = DirectoryServer.getServerRoot() + File.separator +
                            "config" + File.separator + "client.keystore";
      String trustStorePath = DirectoryServer.getServerRoot() + File.separator +
                              "config" + File.separator + "client.truststore";
      String[] args =
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapsPort()),
        "-Z",
        "-K", keyStorePath,
        "-W", "password",
        "-P", trustStorePath,
        "-r",
        "-b", "",
        "-s", "base",
        "(objectClass=*)"
      };
      assertFalse(LDAPSearch.mainSearch(args, false, null, System.err) == 0);
    }
    finally
    {
      disableMapper();
      setAttributeMappings(new String[] { "cn:cn", "e:mail" });
    }
  }
  /**
   * Tests a failed mapping due to no matching users.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testFailedMappingNoMatchingUsers()
         throws Exception
  {
    enableMapper();
    try
    {
      TestCaseUtils.initializeTestBackend(true);
      TestCaseUtils.addEntry(
        "dn: uid=test.user,o=test",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "objectClass: ds-certificate-user",
        "uid: test.user",
        "givenName: Test",
        "sn: User",
        "cn: Not Test User");
      String keyStorePath = DirectoryServer.getServerRoot() + File.separator +
                            "config" + File.separator + "client.keystore";
      String trustStorePath = DirectoryServer.getServerRoot() + File.separator +
                              "config" + File.separator + "client.truststore";
      String[] args =
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapsPort()),
        "-Z",
        "-K", keyStorePath,
        "-W", "password",
        "-P", trustStorePath,
        "-r",
        "-b", "",
        "-s", "base",
        "(objectClass=*)"
      };
      assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
    }
    finally
    {
      disableMapper();
    }
  }
  /**
   * Tests a failed mapping due to multiple matching users.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testFailedMappingMultipleMatchingUsers()
         throws Exception
  {
    enableMapper();
    try
    {
      TestCaseUtils.initializeTestBackend(true);
      TestCaseUtils.addEntries(
        "dn: uid=test.user1,o=test",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "objectClass: ds-certificate-user",
        "uid: test.user1",
        "givenName: Test",
        "sn: User",
        "cn: Test User",
        "",
        "dn: uid=test.user2,o=test",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "objectClass: ds-certificate-user",
        "uid: test.user2",
        "givenName: Test",
        "sn: User",
        "cn: Test User");
      String keyStorePath = DirectoryServer.getServerRoot() + File.separator +
                            "config" + File.separator + "client.keystore";
      String trustStorePath = DirectoryServer.getServerRoot() + File.separator +
                              "config" + File.separator + "client.truststore";
      String[] args =
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapsPort()),
        "-Z",
        "-K", keyStorePath,
        "-W", "password",
        "-P", trustStorePath,
        "-r",
        "-b", "",
        "-s", "base",
        "(objectClass=*)"
      };
      assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
    }
    finally
    {
      disableMapper();
    }
  }
  /**
   * Tests a failed mapping when there are no users below the configured base
   * DNs that match the criteria.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testFailedMappingNoUserBelowBaseDNs()
         throws Exception
  {
    enableMapper();
    try
    {
      setBaseDNs(new String[] { "dc=example,dc=com" });
      TestCaseUtils.initializeTestBackend(true);
      TestCaseUtils.addEntries(
        "dn: uid=test.user,o=test",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "objectClass: ds-certificate-user",
        "uid: test.user",
        "givenName: Test",
        "sn: User",
        "cn: Test User");
      String keyStorePath = DirectoryServer.getServerRoot() + File.separator +
                            "config" + File.separator + "client.keystore";
      String trustStorePath = DirectoryServer.getServerRoot() + File.separator +
                              "config" + File.separator + "client.truststore";
      String[] args =
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapsPort()),
        "-Z",
        "-K", keyStorePath,
        "-W", "password",
        "-P", trustStorePath,
        "-r",
        "-b", "",
        "-s", "base",
        "(objectClass=*)"
      };
      assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
    }
    finally
    {
      disableMapper();
      setBaseDNs(null);
    }
  }
  /**
   * Tests to ensure that an attmept to remove the subject attribute will fail.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testRemoveMapAttribute()
         throws Exception
  {
    String mapperDN = "cn=Subject Attribute to User Attribute," +
                      "cn=Certificate Mappers,cn=config";
    Attribute a =
         new Attribute(DirectoryServer.getAttributeType(
                            "ds-cfg-certificate-subject-attribute-mapping"));
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.DELETE, a));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         conn.processModify(DN.decode(mapperDN), mods);
    assertFalse(modifyOperation.getResultCode() == ResultCode.SUCCESS);
  }
  /**
   * Tests to ensure that an attmept to set an attribute mapping with no colon
   * will fail.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { AssertionError.class })
  public void testSetMappingNoColon()
         throws Exception
  {
    setAttributeMappings(new String[] { "nocolon" });
  }
  /**
   * Tests to ensure that an attmept to set an attribute mapping with no cert
   * attribute will fail.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { AssertionError.class })
  public void testSetMappingNoCertAttribute()
         throws Exception
  {
    setAttributeMappings(new String[] { ":cn" });
  }
  /**
   * Tests to ensure that an attmept to set an attribute mapping with no user
   * attribute will fail.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { AssertionError.class })
  public void testSetMappingNoUserAttribute()
         throws Exception
  {
    setAttributeMappings(new String[] { "cn:" });
  }
  /**
   * Tests to ensure that an attmept to set an attribute mapping with an
   * undefined user attribute will fail.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { AssertionError.class })
  public void testSetMappingUndefinedUserAttribute()
         throws Exception
  {
    setAttributeMappings(new String[] { "cn:undefined" });
  }
  /**
   * Tests to ensure that an attmept to set an attribute mapping with a
   * duplicate cert attribute mapping will fail.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { AssertionError.class })
  public void testSetMappingDuplicateCertAttribute()
         throws Exception
  {
    setAttributeMappings(new String[] { "cn:cn", "cn:sn" });
  }
  /**
   * Tests to ensure that an attmept to set an attribute mapping with a
   * duplicate user attribute mapping will fail.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { AssertionError.class })
  public void testSetMappingDuplicateUserAttribute()
         throws Exception
  {
    setAttributeMappings(new String[] { "cn:cn", "e:cn" });
  }
  /**
   * Tests to ensure that an attmept to set an invalid base DN will fail.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { AssertionError.class })
  public void testSetInvalidBaseDN()
         throws Exception
  {
    setBaseDNs(new String[] { "invalid" });
  }
  /**
   * Alters the configuration of the SASL EXTERNAL mechanism handler so that it
   * uses the Subject Attribute to User Attribute certificate mapper.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  private void enableMapper()
          throws Exception
  {
    String externalDN = "cn=EXTERNAL,cn=SASL Mechanisms,cn=config";
    String mapperDN = "cn=Subject Attribute to User Attribute," +
                      "cn=Certificate Mappers,cn=config";
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                              new Attribute("ds-cfg-certificate-mapper-dn",
                                            mapperDN)));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         conn.processModify(DN.decode(externalDN), mods);
    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Alters the configuration of the SASL EXTERNAL mechanism handler so that it
   * uses the Subject Equals DN certificate mapper.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  private void disableMapper()
          throws Exception
  {
    String externalDN = "cn=EXTERNAL,cn=SASL Mechanisms,cn=config";
    String mapperDN = "cn=Subject Equals DN,cn=Certificate Mappers,cn=config";
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                              new Attribute("ds-cfg-certificate-mapper-dn",
                                            mapperDN)));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         conn.processModify(DN.decode(externalDN), mods);
    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Alters the configuration of the Subject Attribute to User Attribute
   * certificate mapper so that it will use the specified set of mappings.
   *
   * @param  mappings  The specified set of mappings to use.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  private void setAttributeMappings(String[] mappings)
          throws Exception
  {
    String mapperDN = "cn=Subject Attribute to User Attribute," +
                      "cn=Certificate Mappers,cn=config";
    AttributeType attrType =
         DirectoryServer.getAttributeType(
              "ds-cfg-certificate-subject-attribute-mapping");
    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>();
    if (mappings != null)
    {
      for (String mapping : mappings)
      {
        values.add(new AttributeValue(attrType, mapping));
      }
    }
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                              new Attribute(attrType, attrType.getNameOrOID(),
                                            values)));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         conn.processModify(DN.decode(mapperDN), mods);
    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Alters the configuration of the Subject Attribute to User Attribute
   * certificate mapper so that it will look for matches below the specified set
   * of base DNs.
   *
   * @param  baseDNs  The set of base DNs to use when mapping certificates to
   *                  users.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  private void setBaseDNs(String[] baseDNs)
          throws Exception
  {
    String mapperDN = "cn=Subject Attribute to User Attribute," +
                      "cn=Certificate Mappers,cn=config";
    AttributeType attrType =
         DirectoryServer.getAttributeType("ds-cfg-certificate-user-base-dn");
    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>();
    if (baseDNs != null)
    {
      for (String baseDN : baseDNs)
      {
        values.add(new AttributeValue(attrType, baseDN));
      }
    }
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                              new Attribute(attrType, attrType.getNameOrOID(),
                                            values)));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         conn.processModify(DN.decode(mapperDN), mods);
    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SubjectDNToUserAttributeCertificateMapperTestCase.java
New file
@@ -0,0 +1,710 @@
/*
 * 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.io.File;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.opends.server.TestCaseUtils;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ModifyOperation;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.tools.LDAPSearch;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import org.opends.server.types.Modification;
import org.opends.server.types.ModificationType;
import org.opends.server.types.ResultCode;
import static org.testng.Assert.*;
/**
 * A set of test cases for the Subject DN to User Attribute certificate mapper.
 */
public class SubjectDNToUserAttributeCertificateMapperTestCase
       extends ExtensionsTestCase
{
  /**
   * Ensures that the Directory Server is running.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @BeforeClass()
  public void startServer()
         throws Exception
  {
    TestCaseUtils.startServer();
  }
  /**
   * Retrieves a set of invalid configurations that cannot be used to
   * initialize the certificate mapper.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @DataProvider(name = "invalidConfigs")
  public Object[][] getInvalidConfigurations()
         throws Exception
  {
    List<Entry> entries = TestCaseUtils.makeEntries(
      "dn: cn=No Subject Attr,cn=Certificate Mappers,cn=config",
      "objectClass: top",
      "objectClass: ds-cfg-certificate-mapper",
      "objectClass: ds-cfg-subject-dn-to-user-attribute-certificate-mapper",
      "cn: No Subject Attr",
      "ds-cfg-certificate-mapper-class: org.opends.server.extensions." +
           "SubjectDNToUserAttributeCertificateMapper",
      "ds-cfg-certificate-mapper-enabled: true",
      "",
      "dn: cn=Undefined Subject Attr,cn=Certificate Mappers,cn=config",
      "objectClass: top",
      "objectClass: ds-cfg-certificate-mapper",
      "objectClass: ds-cfg-subject-dn-to-user-attribute-certificate-mapper",
      "cn: Undefined Subject Attr",
      "ds-cfg-certificate-mapper-class: org.opends.server.extensions." +
           "SubjectDNToUserAttributeCertificateMapper",
      "ds-cfg-certificate-mapper-enabled: true",
      "ds-cfg-certificate-subject-attribute-type: undefined",
      "",
      "dn: cn=Invalid Base DN,cn=Certificate Mappers,cn=config",
      "objectClass: top",
      "objectClass: ds-cfg-certificate-mapper",
      "objectClass: ds-cfg-subject-dn-to-user-attribute-certificate-mapper",
      "cn: Invalid Base DN",
      "ds-cfg-certificate-mapper-class: org.opends.server.extensions." +
           "SubjectDNToUserAttributeCertificateMapper",
      "ds-cfg-certificate-mapper-enabled: true",
      "ds-cfg-certificate-subject-attribute-type: ds-certificate-subject-dn",
      "ds-cfg-certificate-user-base-dn: invalid");
    Object[][] configEntries = new Object[entries.size()][1];
    for (int i=0; i < configEntries.length; i++)
    {
      configEntries[i] = new Object[] { entries.get(i) };
    }
    return configEntries;
  }
  /**
   * Tests initialization with an invalid configuration.
   *
   * @param  e  The configuration entry to use to initialize the certificate
   *            mapper.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "invalidConfigs",
        expectedExceptions = { ConfigException.class,
                               InitializationException.class })
  public void testInvalidConfigs(Entry e)
         throws Exception
  {
    DN parentDN = DN.decode("cn=Certificate Mappers,cn=config");
    ConfigEntry parentEntry = DirectoryServer.getConfigEntry(parentDN);
    ConfigEntry configEntry = new ConfigEntry(e, parentEntry);
    SubjectDNToUserAttributeCertificateMapper mapper =
         new SubjectDNToUserAttributeCertificateMapper();
    mapper.initializeCertificateMapper(configEntry);
  }
  /**
   * Tests a successful mapping using the default configuration.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testSuccessfulMappingDefaultConfig()
         throws Exception
  {
    enableMapper();
    try
    {
      TestCaseUtils.initializeTestBackend(true);
      TestCaseUtils.addEntry(
        "dn: uid=test.user,o=test",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "objectClass: ds-certificate-user",
        "uid: test.user",
        "givenName: Test",
        "sn: User",
        "cn: Test User",
        "ds-certificate-subject-dn: CN=Test User, O=Test");
      String keyStorePath = DirectoryServer.getServerRoot() + File.separator +
                            "config" + File.separator + "client.keystore";
      String trustStorePath = DirectoryServer.getServerRoot() + File.separator +
                              "config" + File.separator + "client.truststore";
      String[] args =
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapsPort()),
        "-Z",
        "-K", keyStorePath,
        "-W", "password",
        "-P", trustStorePath,
        "-r",
        "-b", "",
        "-s", "base",
        "(objectClass=*)"
      };
      assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
    }
    finally
    {
      disableMapper();
    }
  }
  /**
   * Tests a successful mapping using a configuration with a different subject
   * attribute.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testSuccessfulMappingAlternateSubjectAttribute()
         throws Exception
  {
    enableMapper();
    try
    {
      setSubjectAttribute("manager");
      TestCaseUtils.initializeTestBackend(true);
      TestCaseUtils.addEntry(
        "dn: uid=test.user,o=test",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "objectClass: ds-certificate-user",
        "uid: test.user",
        "givenName: Test",
        "sn: User",
        "cn: Test User",
        "manager: CN=Test User, O=Test");
      String keyStorePath = DirectoryServer.getServerRoot() + File.separator +
                            "config" + File.separator + "client.keystore";
      String trustStorePath = DirectoryServer.getServerRoot() + File.separator +
                              "config" + File.separator + "client.truststore";
      String[] args =
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapsPort()),
        "-Z",
        "-K", keyStorePath,
        "-W", "password",
        "-P", trustStorePath,
        "-r",
        "-b", "",
        "-s", "base",
        "(objectClass=*)"
      };
      assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
    }
    finally
    {
      disableMapper();
      setSubjectAttribute("ds-certificate-subject-dn");
    }
  }
  /**
   * Tests a successful mapping using a configuration with a different set of
   * base DNs.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testSuccessfulMappingAlternateBaseDNs()
         throws Exception
  {
    enableMapper();
    try
    {
      setBaseDNs(new String[] { "o=test" });
      TestCaseUtils.initializeTestBackend(true);
      TestCaseUtils.addEntry(
        "dn: uid=test.user,o=test",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "objectClass: ds-certificate-user",
        "uid: test.user",
        "givenName: Test",
        "sn: User",
        "cn: Test User",
        "ds-certificate-subject-dn: CN=Test User, O=Test");
      String keyStorePath = DirectoryServer.getServerRoot() + File.separator +
                            "config" + File.separator + "client.keystore";
      String trustStorePath = DirectoryServer.getServerRoot() + File.separator +
                              "config" + File.separator + "client.truststore";
      String[] args =
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapsPort()),
        "-Z",
        "-K", keyStorePath,
        "-W", "password",
        "-P", trustStorePath,
        "-r",
        "-b", "",
        "-s", "base",
        "(objectClass=*)"
      };
      assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
    }
    finally
    {
      disableMapper();
      setSubjectAttribute("ds-certificate-subject-dn");
    }
  }
  /**
   * Tests a failed mapping when there are no users that should match.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testFailedMappingNoUsers()
         throws Exception
  {
    enableMapper();
    try
    {
      TestCaseUtils.initializeTestBackend(true);
      TestCaseUtils.addEntry(
        "dn: cn=Test User,o=test",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "objectClass: ds-certificate-user",
        "uid: test.user",
        "givenName: Test",
        "sn: User",
        "cn: Test User");
      String keyStorePath = DirectoryServer.getServerRoot() + File.separator +
                            "config" + File.separator + "client.keystore";
      String trustStorePath = DirectoryServer.getServerRoot() + File.separator +
                              "config" + File.separator + "client.truststore";
      String[] args =
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapsPort()),
        "-Z",
        "-K", keyStorePath,
        "-W", "password",
        "-P", trustStorePath,
        "-r",
        "-b", "",
        "-s", "base",
        "(objectClass=*)"
      };
      assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
    }
    finally
    {
      disableMapper();
    }
  }
  /**
   * Tests a failed mapping when there are multiple users that match the
   * critieria.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testFailedMappingMultipleUsers()
         throws Exception
  {
    enableMapper();
    try
    {
      TestCaseUtils.initializeTestBackend(true);
      TestCaseUtils.addEntries(
        "dn: uid=test.user1,o=test",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "objectClass: ds-certificate-user",
        "uid: test.user1",
        "givenName: Test",
        "sn: User",
        "cn: Test User 1",
        "ds-certificate-subject-dn: CN=Test User, O=Test",
        "",
        "dn: uid=test.user2,o=test",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "objectClass: ds-certificate-user",
        "uid: test.user2",
        "givenName: Test",
        "sn: User",
        "cn: Test User 2",
        "ds-certificate-subject-dn: CN=Test User, O=Test");
      String keyStorePath = DirectoryServer.getServerRoot() + File.separator +
                            "config" + File.separator + "client.keystore";
      String trustStorePath = DirectoryServer.getServerRoot() + File.separator +
                              "config" + File.separator + "client.truststore";
      String[] args =
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapsPort()),
        "-Z",
        "-K", keyStorePath,
        "-W", "password",
        "-P", trustStorePath,
        "-r",
        "-b", "",
        "-s", "base",
        "(objectClass=*)"
      };
      assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
    }
    finally
    {
      disableMapper();
    }
  }
  /**
   * Tests a failed mapping when there are no users below the configured base
   * DNs that match the criteria.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testFailedMappingNoUserBelowBaseDNs()
         throws Exception
  {
    enableMapper();
    try
    {
      setBaseDNs(new String[] { "dc=example,dc=com" });
      TestCaseUtils.initializeTestBackend(true);
      TestCaseUtils.addEntries(
        "dn: uid=test.user,o=test",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "objectClass: ds-certificate-user",
        "uid: test.user",
        "givenName: Test",
        "sn: User",
        "cn: Test User",
        "ds-certificate-subject-dn: CN=Test User, O=Test");
      String keyStorePath = DirectoryServer.getServerRoot() + File.separator +
                            "config" + File.separator + "client.keystore";
      String trustStorePath = DirectoryServer.getServerRoot() + File.separator +
                              "config" + File.separator + "client.truststore";
      String[] args =
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapsPort()),
        "-Z",
        "-K", keyStorePath,
        "-W", "password",
        "-P", trustStorePath,
        "-r",
        "-b", "",
        "-s", "base",
        "(objectClass=*)"
      };
      assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
    }
    finally
    {
      disableMapper();
      setBaseDNs(null);
    }
  }
  /**
   * Tests to ensure that an attmept to remove the subject attribute will fail.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testRemoveSubjectAttribute()
         throws Exception
  {
    String mapperDN =
         "cn=Subject DN to User Attribute,cn=Certificate Mappers,cn=config";
    Attribute a =
         new Attribute(DirectoryServer.getAttributeType(
                            "ds-cfg-certificate-subject-attribute-type"));
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.DELETE, a));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         conn.processModify(DN.decode(mapperDN), mods);
    assertFalse(modifyOperation.getResultCode() == ResultCode.SUCCESS);
  }
  /**
   * Tests to ensure that an attmept to set an undefined subject attribute will
   * fail.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { AssertionError.class })
  public void testSetUndefinedSubjectAttribute()
         throws Exception
  {
    setSubjectAttribute("undefined");
  }
  /**
   * Tests to ensure that an attmept to set an invalid base DN will fail.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { AssertionError.class })
  public void testSetInvalidBaseDN()
         throws Exception
  {
    setBaseDNs(new String[] { "invalid" });
  }
  /**
   * Alters the configuration of the SASL EXTERNAL mechanism handler so that it
   * uses the Subject DN to User Attribute certificate mapper.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  private void enableMapper()
          throws Exception
  {
    String externalDN = "cn=EXTERNAL,cn=SASL Mechanisms,cn=config";
    String mapperDN =
         "cn=Subject DN to User Attribute,cn=Certificate Mappers,cn=config";
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                              new Attribute("ds-cfg-certificate-mapper-dn",
                                            mapperDN)));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         conn.processModify(DN.decode(externalDN), mods);
    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Alters the configuration of the SASL EXTERNAL mechanism handler so that it
   * uses the Subject Equals DN certificate mapper.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  private void disableMapper()
          throws Exception
  {
    String externalDN = "cn=EXTERNAL,cn=SASL Mechanisms,cn=config";
    String mapperDN = "cn=Subject Equals DN,cn=Certificate Mappers,cn=config";
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                              new Attribute("ds-cfg-certificate-mapper-dn",
                                            mapperDN)));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         conn.processModify(DN.decode(externalDN), mods);
    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Alters the configuration of the Subject DN to User Attribute certificate
   * mapper so that it will look for the subject DN in the specified attribute.
   *
   * @param  attrName  The name of the attribute in which to look for the
   *                   certificate subject.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  private void setSubjectAttribute(String attrName)
          throws Exception
  {
    String mapperDN =
         "cn=Subject DN to User Attribute,cn=Certificate Mappers,cn=config";
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                      new Attribute("ds-cfg-certificate-subject-attribute-type",
                                    attrName)));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         conn.processModify(DN.decode(mapperDN), mods);
    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Alters the configuration of the Subject DN to User Attribute certificate
   * mapper so that it will look for the subject DN below the specified set of
   * base DNs.
   *
   * @param  baseDNs  The set of base DNs to use when mapping certificates to
   *                  users.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  private void setBaseDNs(String[] baseDNs)
          throws Exception
  {
    String mapperDN =
         "cn=Subject DN to User Attribute,cn=Certificate Mappers,cn=config";
    AttributeType attrType =
         DirectoryServer.getAttributeType("ds-cfg-certificate-user-base-dn");
    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>();
    if (baseDNs != null)
    {
      for (String baseDN : baseDNs)
      {
        values.add(new AttributeValue(attrType, baseDN));
      }
    }
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                              new Attribute(attrType, attrType.getNameOrOID(),
                                            values)));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         conn.processModify(DN.decode(mapperDN), mods);
    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
  }
}