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

neil_a_wilson
04.24.2007 77ee765da575152ae0e8eff25d00c4e8cf2a7b11
Add a 7-bit clean plugin, which can be used to ensure that values for a
specified set of attributes (optionally only for entries below a specified set
of base DNs) are 7-bit clean.

OpenDS Issue Number: 261
3 files added
3 files modified
1510 ■■■■■ changed files
opends/resource/config/config.ldif 39 ●●●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif 9 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/SevenBitCleanPluginConfiguration.xml 125 ●●●●● patch | view | raw | blame | history
opends/src/messages/messages/plugin.properties 68 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/plugins/SevenBitCleanPlugin.java 552 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/SevenBitCleanPluginTestCase.java 717 ●●●●● patch | view | raw | blame | history
opends/resource/config/config.ldif
@@ -1419,6 +1419,21 @@
objectClass: ds-cfg-plugin-root
cn: Plugins
dn: cn=7-Bit Clean,cn=Plugins,cn=config
objectClass: top
objectClass: ds-cfg-plugin
objectClass: ds-cfg-7-bit-clean-plugin
cn: 7-Bit Clean
ds-cfg-plugin-class: org.opends.server.plugins.SevenBitCleanPlugin
ds-cfg-plugin-enabled: false
ds-cfg-plugin-type: ldifImport
ds-cfg-plugin-type: preParseAdd
ds-cfg-plugin-type: preParseModify
ds-cfg-plugin-type: preParseModifyDN
ds-cfg-7-bit-clean-attribute-type: uid
ds-cfg-7-bit-clean-attribute-type: mail
ds-cfg-7-bit-clean-attribute-type: userPassword
dn: cn=Entry UUID,cn=Plugins,cn=config
objectClass: top
objectClass: ds-cfg-plugin
@@ -1470,18 +1485,6 @@
ds-cfg-profile-directory: logs
ds-cfg-profile-sample-interval: 10 milliseconds
dn: cn=UID Unique Attribute,cn=Plugins,cn=config
objectClass: top
objectClass: ds-cfg-plugin
objectClass: ds-cfg-unique-attribute-plugin
cn: UID Unique Attribute
ds-cfg-plugin-class: org.opends.server.plugins.UniqueAttributePlugin
ds-cfg-plugin-enabled: false
ds-cfg-plugin-type: preOperationAdd
ds-cfg-plugin-type: preOperationModify
ds-cfg-plugin-type: preOperationModifyDN
ds-cfg-unique-attribute-type: uid
dn: cn=Referential Integrity,cn=Plugins,cn=config
objectClass: top
objectClass: ds-cfg-plugin
@@ -1495,6 +1498,18 @@
ds-cfg-referential-integrity-attribute-type: member
ds-cfg-referential-integrity-attribute-type: uniqueMember
dn: cn=UID Unique Attribute,cn=Plugins,cn=config
objectClass: top
objectClass: ds-cfg-plugin
objectClass: ds-cfg-unique-attribute-plugin
cn: UID Unique Attribute
ds-cfg-plugin-class: org.opends.server.plugins.UniqueAttributePlugin
ds-cfg-plugin-enabled: false
ds-cfg-plugin-type: preOperationAdd
ds-cfg-plugin-type: preOperationModify
ds-cfg-plugin-type: preOperationModifyDN
ds-cfg-unique-attribute-type: uid
dn: cn=Root DNs,cn=config
objectClass: top
objectClass: ds-cfg-root-dn-base
opends/resource/schema/02-config.ldif
@@ -1646,6 +1646,12 @@
  NAME 'ds-cfg-max-blocked-write-time-limit'
  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.487
  NAME 'ds-cfg-7-bit-clean-attribute-type' 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.488
  NAME 'ds-cfg-7-bit-clean-base-dn' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
  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 )
@@ -2459,4 +2465,7 @@
  $ ds-cfg-referential-integrity-update-interval
  $ ds-cfg-referential-integrity-log-file )
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.167 NAME 'ds-cfg-7-bit-clean-plugin'
  SUP ds-cfg-plugin STRUCTURAL MUST ds-cfg-7-bit-clean-attribute-type
  MAY ds-cfg-7-bit-clean-base-dn X-ORIGIN 'OpenDS Directory Server' )
opends/src/admin/defn/org/opends/server/admin/std/SevenBitCleanPluginConfiguration.xml
New file
@@ -0,0 +1,125 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
! CDDL HEADER START
!
! The contents of this file are subject to the terms of the
! Common Development and Distribution License, Version 1.0 only
! (the "License").  You may not use this file except in compliance
! with the License.
!
! You can obtain a copy of the license at
! trunk/opends/resource/legal-notices/OpenDS.LICENSE
! or https://OpenDS.dev.java.net/OpenDS.LICENSE.
! See the License for the specific language governing permissions
! and limitations under the License.
!
! When distributing Covered Code, include this CDDL HEADER in each
! file and include the License file at
! trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
! add the following below this CDDL HEADER, with the fields enclosed
! by brackets "[]" replaced with your own identifying information:
!      Portions Copyright [yyyy] [name of copyright owner]
!
! CDDL HEADER END
!
!
!      Portions Copyright 2007 Sun Microsystems, Inc.
! -->
<adm:managed-object
  name="seven-bit-clean-plugin"
  plural-name="seven-bit-clean-plugins"
  package="org.opends.server.admin.std"
  extends="plugin"
  xmlns:adm="http://www.opends.org/admin"
  xmlns:ldap="http://www.opends.org/admin-ldap">
  <adm:synopsis>
    The <adm:user-friendly-name /> may be used to ensure that values for a
    specified set of attributes are 7-bit clean.  That is, for those attributes,
    the values are not allowed to contain any bytes having the high-order bit
    set, which is used to indicate the presence of non-ASCII characters.  Some
    applications may not properly handle attribute values that contain non-ASCII
    characters, and this plugin may help ensure that attributes used by those
    applications do not contain characters which may cause problems in those
    applications.
  </adm:synopsis>
  <adm:profile name="ldap">
    <ldap:object-class>
      <ldap:oid>1.3.6.1.4.1.26027.1.2.167</ldap:oid>
      <ldap:name>ds-cfg-7-bit-clean-plugin</ldap:name>
      <ldap:superior>ds-cfg-plugin</ldap:superior>
    </ldap:object-class>
  </adm:profile>
  <adm:property-override name="plugin-class">
    <adm:default-behavior>
      <adm:defined>
        <adm:value>
          org.opends.server.plugins.SevenBitCleanPlugin
        </adm:value>
      </adm:defined>
    </adm:default-behavior>
  </adm:property-override>
  <adm:property-override name="plugin-type">
    <adm:default-behavior>
      <adm:defined>
        <adm:value>ldifimport</adm:value>
        <adm:value>preparseadd</adm:value>
        <adm:value>preparsemodify</adm:value>
        <adm:value>preparsemodifydn</adm:value>
      </adm:defined>
    </adm:default-behavior>
  </adm:property-override>
  <adm:property name="attribute-type" mandatory="true" multi-valued="true">
    <adm:synopsis>
      Specifies the name or OID of an attribute type for which values should be
      checked to ensure that they are 7-bit clean.
    </adm:synopsis>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>uid</adm:value>
        <adm:value>mail</adm:value>
        <adm:value>userPassword</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:attribute-type />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.487</ldap:oid>
        <ldap:name>ds-cfg-7-bit-clean-attribute-type</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="base-dn" mandatory="false" multi-valued="true">
    <adm:synopsis>
      Specifies the base DN below which the checking will be performed.  Any
      attempt to update a value for one of the configured attributes below this
      base DN must be 7-bit clean for the operation to be allowed.
    </adm:synopsis>
    <adm:default-behavior>
      <adm:alias>
        <adm:synopsis>
          All entries below all public naming contexts will be checked.
        </adm:synopsis>
      </adm:alias>
    </adm:default-behavior>
    <adm:syntax>
      <adm:dn />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.488</ldap:oid>
        <ldap:name>ds-cfg-7-bit-clean-base-dn</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
</adm:managed-object>
opends/src/messages/messages/plugin.properties
@@ -319,42 +319,58 @@
 register the Referential Integrity plugin to be invoked as a %s plugin.  This \
 plugin type is not allowed for this plugin
SEVERE_ERR_PLUGIN_REFERENT_CREATE_LOGFILE_82=An error occured during \
   Referential Integity plugin initialization because log file creation \
   failed:  %s
 Referential Integity plugin initialization because log file creation \
 failed:  %s
SEVERE_ERR_PLUGIN_REFERENT_CLOSE_LOGFILE_83=An error occured closing the \
  Referential Integrity plugin update logile: %s
 Referential Integrity plugin update logile: %s
SEVERE_ERR_PLUGIN_REFERENT_REPLACE_LOGFILE_84=An error occured replacing the \
  Referential Integrity plugin update logile: %s
 Referential Integrity plugin update logile: %s
INFO_PLUGIN_REFERENT_LOGFILE_CHANGE_REQUIRES_RESTART_85=The file name that \
  the Referential Integrity plugin logs changes to during background \
  processing has been changed from %s to %s, but this change will not take \
  effect until the server is restarted
 the Referential Integrity plugin logs changes to during background \
 processing has been changed from %s to %s, but this change will not take \
 effect until the server is restarted
INFO_PLUGIN_REFERENT_BACKGROUND_PROCESSING_UPDATE_INTERVAL_CHANGED_86=The \
  Referential Integrity plugin background processing update interval has \
  been changed from %s to %s, the new value will now be during background \
  processing
 Referential Integrity plugin background processing update interval has \
 been changed from %s to %s, the new value will now be during background \
 processing
INFO_PLUGIN_REFERENT_BACKGROUND_PROCESSING_STOPPING_87=The Referential \
  Integrity plugin background processing has been stopped
 Integrity plugin background processing has been stopped
INFO_PLUGIN_REFERENT_BACKGROUND_PROCESSING_STARTING_88=The Referential \
  Integrity plugin has started background processing using the update \
  interval %s
 Integrity plugin has started background processing using the update \
 interval %s
SEVERE_ERR_PLUGIN_REFERENT_SEARCH_FAILED_89=The Referential \
  Integrity plugin failed when performaing an internal search: %s
 Integrity plugin failed when performaing an internal search: %s
SEVERE_ERR_PLUGIN_REFERENT_MODIFY_FAILED_90=The Referential \
  Integrity plugin failed when performing an internal modify on entry %s: %s
 Integrity plugin failed when performing an internal modify on entry %s: %s
MILD_ERR_PLUGIN_REFERENT_CANNOT_DECODE_STRING_AS_DN_91=The Referential \
  Integrity plugin failed to decode a entry DN from the update log: %s
 Integrity plugin failed to decode a entry DN from the update log: %s
INFO_PLUGIN_REFERENT_SEARCH_NO_SUCH_OBJECT_92=The Referential Integrity \
  plugin failed when performing a search because the base DN %s does not exist
 plugin failed when performing a search because the base DN %s does not exist
SEVERE_ERR_PLUGIN_REFERENT_INVALID_ATTRIBUTE_SYNTAX_93=An error occured \
  in the Referential Integrity plugin while attempting to configure the \
  attribute type %s which has a syntax OID of %s. A Referential Integrity \
  attribute type must have a syntax OID of either \
  1.3.6.1.4.1.1466.115.121.1.12 (for the distinguished name syntax) or \
  1.3.6.1.4.1.1466.115.121.1.34 (for the name and optional uid syntax)
 in the Referential Integrity plugin while attempting to configure the \
 attribute type %s which has a syntax OID of %s. A Referential Integrity \
 attribute type must have a syntax OID of either \
 1.3.6.1.4.1.1466.115.121.1.12 (for the distinguished name syntax) or \
 1.3.6.1.4.1.1466.115.121.1.34 (for the name and optional uid syntax)
SEVERE_ERR_PLUGIN_REFERENT_SKIP_DELETE_PROCESSING_94=The Referential Integrity \
  plugin will not process a post delete operation on entry %s because the core \
  operation failed
 plugin will not process a post delete operation on entry %s because the core \
 operation failed
SEVERE_ERR_PLUGIN_REFERENT_SKIP_MODIFY_DN_PROCESSING_95=The Referential \
  Integrity plugin will not process a post modify DN operation on entry %s \
  because the core operation failed
 Integrity plugin will not process a post modify DN operation on entry %s \
 because the core operation failed
MILD_ERR_PLUGIN_7BIT_INVALID_PLUGIN_TYPE_96=The 7-bit clean plugin is \
 configured with invalid plugin type %s.  Only the ldifImport, \
 preOperationAdd, preOperationModify, and preOperationModifyDN plugin types \
 are allowed
MILD_ERR_PLUGIN_7BIT_CANNOT_DECODE_DN_97=An error occurred while trying to \
 decode the DN of the target entry:  %s
MILD_ERR_PLUGIN_7BIT_CANNOT_DECODE_ATTR_98=An error occurred while trying to \
 decode attribute %s in the target entry:  %s
MILD_ERR_PLUGIN_7BIT_CANNOT_DECODE_NEW_RDN_99=An error occurred while trying \
 to decode the new RDN:  %s
MILD_ERR_PLUGIN_7BIT_ADD_ATTR_NOT_CLEAN_100=The entry to be added included a \
 value for attribute %s that was not 7-bit clean
MILD_ERR_PLUGIN_7BIT_MODIFY_ATTR_NOT_CLEAN_101=The modify operation would \
 have resulted in a value for attribute %s that was not 7-bit clean
MILD_ERR_PLUGIN_7BIT_MODIFYDN_ATTR_NOT_CLEAN_102=The modify DN operation \
 would have resulted in a value for attribute %s that was not 7-bit clean
opends/src/server/org/opends/server/plugins/SevenBitCleanPlugin.java
New file
@@ -0,0 +1,552 @@
/*
 * 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.plugins;
import java.util.List;
import java.util.Set;
import org.opends.messages.Message;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.meta.PluginCfgDefn;
import org.opends.server.admin.std.server.SevenBitCleanPluginCfg;
import org.opends.server.admin.std.server.PluginCfg;
import org.opends.server.api.plugin.DirectoryServerPlugin;
import org.opends.server.api.plugin.LDIFPluginResult;
import org.opends.server.api.plugin.PluginType;
import org.opends.server.api.plugin.PreParsePluginResult;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.ByteString;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.LDAPException;
import org.opends.server.types.LDIFImportConfig;
import org.opends.server.types.RawAttribute;
import org.opends.server.types.RawModification;
import org.opends.server.types.RDN;
import org.opends.server.types.ResultCode;
import org.opends.server.types.operation.PreParseAddOperation;
import org.opends.server.types.operation.PreParseModifyOperation;
import org.opends.server.types.operation.PreParseModifyDNOperation;
import static org.opends.messages.PluginMessages.*;
/**
 * This class implements a Directory Server plugin that can be used to ensure
 * that the values for a specified set of attributes (optionally, below a
 * specified set of base DNs) are 7-bit clean (i.e., contain only ASCII
 * characters).
 */
public final class SevenBitCleanPlugin
       extends DirectoryServerPlugin<SevenBitCleanPluginCfg>
       implements ConfigurationChangeListener<SevenBitCleanPluginCfg>
{
  /**
   * The bitmask that will be used to make the comparisons.
   */
  private static final byte MASK = (byte) 0x7F;
  /**
   * The result that should be returned if an imported entry fails the 7-bit
   * clean check.
   */
  private static final LDIFPluginResult LDIF_FAILURE_RESULT =
       new LDIFPluginResult(false, false);
  /**
   * The result that should be returned if a pre-parse operation fails the 7-bit
   * clean check.
   */
  private static final PreParsePluginResult PRE_PARSE_FAILURE_RESULT =
       new PreParsePluginResult(false, false, false, true);
  // The current configuration for this plugin.
  private SevenBitCleanPluginCfg currentConfig;
  /**
   * Creates a new instance of this Directory Server plugin.  Every plugin must
   * implement a default constructor (it is the only one that will be used to
   * create plugins defined in the configuration), and every plugin constructor
   * must call {@code super()} as its first element.
   */
  public SevenBitCleanPlugin()
  {
    super();
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final void initializePlugin(Set<PluginType> pluginTypes,
                                     SevenBitCleanPluginCfg configuration)
         throws ConfigException
  {
    currentConfig = configuration;
    configuration.addSevenBitCleanChangeListener(this);
    // Make sure that the plugin has been enabled for the appropriate types.
    for (PluginType t : pluginTypes)
    {
      switch (t)
      {
        case LDIF_IMPORT:
        case PRE_PARSE_ADD:
        case PRE_PARSE_MODIFY:
        case PRE_PARSE_MODIFY_DN:
          // These are acceptable.
          break;
        default:
          Message message =
              ERR_PLUGIN_7BIT_INVALID_PLUGIN_TYPE.get(t.toString());
          throw new ConfigException(message);
      }
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final void finalizePlugin()
  {
    currentConfig.removeSevenBitCleanChangeListener(this);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final LDIFPluginResult doLDIFImport(LDIFImportConfig importConfig,
                                             Entry entry)
  {
    // Get the current configuration for this plugin.
    SevenBitCleanPluginCfg config = currentConfig;
    // Make sure that the entry is within the scope of this plugin.  While
    // processing an LDIF import, we don't have access to the set of public
    // naming contexts defined in the server, so if no explicit set of base DNs
    // is defined, then assume that the entry is in scope.
    Set<DN> baseDNs = config.getBaseDN();
    if ((baseDNs != null) && (! baseDNs.isEmpty()))
    {
      boolean found = true;
      for (DN baseDN : baseDNs)
      {
        if (baseDN.isAncestorOf(entry.getDN()))
        {
          found = true;
          break;
        }
      }
      if (! found)
      {
        // The entry is out of scope, so we won't process it.
        return LDIFPluginResult.SUCCESS;
      }
    }
    // Make sure all configured attributes have clean values.
    for (AttributeType t : config.getAttributeType())
    {
      List<Attribute> attrList = entry.getAttribute(t);
      if (attrList != null)
      {
        for (Attribute a : attrList)
        {
          for (AttributeValue v : a.getValues())
          {
            if (! is7BitClean(v.getValue()))
            {
              return LDIF_FAILURE_RESULT;
            }
          }
        }
      }
    }
    // If we've gotten here, then everything is acceptable.
System.err.println("  The entry is acceptable");
    return LDIFPluginResult.SUCCESS;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final PreParsePluginResult
                    doPreParse(PreParseAddOperation addOperation)
  {
    // Get the current configuration for this plugin.
    SevenBitCleanPluginCfg config = currentConfig;
    // If the entry is within the scope of this plugin, then make sure all
    // configured attributes have clean values.
    DN entryDN;
    try
    {
      entryDN = DN.decode(addOperation.getRawEntryDN());
    }
    catch (DirectoryException de)
    {
      addOperation.appendErrorMessage(
           ERR_PLUGIN_7BIT_CANNOT_DECODE_DN.get(de.getMessageObject()));
      return PRE_PARSE_FAILURE_RESULT;
    }
    if (isInScope(config, entryDN))
    {
      for (RawAttribute rawAttr : addOperation.getRawAttributes())
      {
        Attribute a;
        try
        {
          a = rawAttr.toAttribute();
        }
        catch (LDAPException le)
        {
          addOperation.appendErrorMessage(
               ERR_PLUGIN_7BIT_CANNOT_DECODE_DN.get(le.getMessageObject()));
          return PRE_PARSE_FAILURE_RESULT;
        }
        if (! config.getAttributeType().contains(a.getAttributeType()))
        {
          continue;
        }
        for (AttributeValue v : a.getValues())
        {
          if (! is7BitClean(v.getValue()))
          {
            addOperation.appendErrorMessage(
                 ERR_PLUGIN_7BIT_ADD_ATTR_NOT_CLEAN.get(
                      rawAttr.getAttributeType()));
            return PRE_PARSE_FAILURE_RESULT;
          }
        }
      }
    }
    // If we've gotten here, then everything is acceptable.
    return PreParsePluginResult.SUCCESS;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final PreParsePluginResult
                    doPreParse(PreParseModifyOperation modifyOperation)
  {
    // Get the current configuration for this plugin.
    SevenBitCleanPluginCfg config = currentConfig;
    // If the target entry is within the scope of this plugin, then make sure
    // all values that will be added during the modification will be acceptable.
    DN entryDN;
    try
    {
      entryDN = DN.decode(modifyOperation.getRawEntryDN());
    }
    catch (DirectoryException de)
    {
      modifyOperation.appendErrorMessage(
           ERR_PLUGIN_7BIT_CANNOT_DECODE_DN.get(de.getMessageObject()));
      return PRE_PARSE_FAILURE_RESULT;
    }
    if (isInScope(config, entryDN))
    {
      for (RawModification m : modifyOperation.getRawModifications())
      {
        switch (m.getModificationType())
        {
          case ADD:
          case REPLACE:
            // These are modification types that we will process.
            break;
          default:
            // This is not a modifiation type that we will process.
            continue;
        }
        RawAttribute rawAttr = m.getAttribute();
        Attribute a;
        try
        {
          a = rawAttr.toAttribute();
        }
        catch (LDAPException le)
        {
          modifyOperation.appendErrorMessage(
               ERR_PLUGIN_7BIT_CANNOT_DECODE_DN.get(le.getMessageObject()));
          return PRE_PARSE_FAILURE_RESULT;
        }
        if (! config.getAttributeType().contains(a.getAttributeType()))
        {
          continue;
        }
        for (AttributeValue v : a.getValues())
        {
          if (! is7BitClean(v.getValue()))
          {
            modifyOperation.appendErrorMessage(
                 ERR_PLUGIN_7BIT_MODIFY_ATTR_NOT_CLEAN.get(
                      rawAttr.getAttributeType()));
            return PRE_PARSE_FAILURE_RESULT;
          }
        }
      }
    }
    // If we've gotten here, then everything is acceptable.
    return PreParsePluginResult.SUCCESS;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final PreParsePluginResult
                    doPreParse(PreParseModifyDNOperation modifyDNOperation)
  {
    // Get the current configuration for this plugin.
    SevenBitCleanPluginCfg config = currentConfig;
    // If the target entry is within the scope of this plugin, then make sure
    // all values that will be added during the modification will be acceptable.
    DN entryDN;
    try
    {
      entryDN = DN.decode(modifyDNOperation.getRawEntryDN());
    }
    catch (DirectoryException de)
    {
      modifyDNOperation.appendErrorMessage(
           ERR_PLUGIN_7BIT_CANNOT_DECODE_DN.get(de.getMessageObject()));
      return PRE_PARSE_FAILURE_RESULT;
    }
    if (isInScope(config, entryDN))
    {
      ByteString rawNewRDN = modifyDNOperation.getRawNewRDN();
      RDN newRDN;
      try
      {
        newRDN = RDN.decode(rawNewRDN.stringValue());
      }
      catch (DirectoryException de)
      {
        modifyDNOperation.appendErrorMessage(
             ERR_PLUGIN_7BIT_CANNOT_DECODE_NEW_RDN.get(de.getMessageObject()));
        return PRE_PARSE_FAILURE_RESULT;
      }
      int numValues = newRDN.getNumValues();
      for (int i=0; i < numValues; i++)
      {
        if (! config.getAttributeType().contains(newRDN.getAttributeType(i)))
        {
          continue;
        }
        if (! is7BitClean(newRDN.getAttributeValue(i).getValue()))
        {
          modifyDNOperation.appendErrorMessage(
               ERR_PLUGIN_7BIT_MODIFYDN_ATTR_NOT_CLEAN.get(
                    newRDN.getAttributeName(i)));
          return PRE_PARSE_FAILURE_RESULT;
        }
      }
    }
    // If we've gotten here, then everything is acceptable.
    return PreParsePluginResult.SUCCESS;
  }
  /**
   * Indicates whether the provided DN is within the scope of this plugin.
   *
   * @param  config  The configuration to use when making the determination.
   * @param  dn      The DN for which to make the determination.
   *
   * @return  {@code true} if the provided DN is within the scope of this
   *          plugin, or {@code false} if  not.
   */
  private final boolean isInScope(SevenBitCleanPluginCfg config, DN dn)
  {
    Set<DN> baseDNs = config.getBaseDN();
    if ((baseDNs == null) || baseDNs.isEmpty())
    {
      baseDNs = DirectoryServer.getPublicNamingContexts().keySet();
    }
    boolean found = false;
    for (DN baseDN: baseDNs)
    {
      if (dn.isDescendantOf(baseDN))
      {
        found = true;
        break;
      }
    }
    return found;
  }
  /**
   * Indicates whether the provided value is 7-bit clean.
   *
   * @param  value  The value for which to make the determination.
   *
   * @return {@code true} if the provided value is 7-bit clean, or {@code false}
   *         if it is not.
   */
  private final boolean is7BitClean(ByteString value)
  {
    for (byte b : value.value())
    {
      int i = (b & 0xFF);
      if ((b & MASK) != b)
      {
        return false;
      }
    }
    return true;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean isConfigurationAcceptable(PluginCfg configuration,
                                           List<Message> unacceptableReasons)
  {
    SevenBitCleanPluginCfg cfg = (SevenBitCleanPluginCfg) configuration;
    return isConfigurationChangeAcceptable(cfg, unacceptableReasons);
  }
  /**
   * {@inheritDoc}
   */
  public boolean isConfigurationChangeAcceptable(
                      SevenBitCleanPluginCfg configuration,
                      List<Message> unacceptableReasons)
  {
    boolean configAcceptable = true;
    // Ensure that the set of plugin types is acceptable.
    for (PluginCfgDefn.PluginType pluginType : configuration.getPluginType())
    {
      switch (pluginType)
      {
        case LDIFIMPORT:
        case PREPARSEADD:
        case PREPARSEMODIFY:
        case PREPARSEMODIFYDN:
          // These are acceptable.
          break;
        default:
          Message message = ERR_PLUGIN_7BIT_INVALID_PLUGIN_TYPE.get(
                  pluginType.toString());
          unacceptableReasons.add(message);
          configAcceptable = false;
      }
    }
    return configAcceptable;
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationChange(
                                 SevenBitCleanPluginCfg configuration)
  {
    currentConfig = configuration;
    return new ConfigChangeResult(ResultCode.SUCCESS, false);
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/SevenBitCleanPluginTestCase.java
New file
@@ -0,0 +1,717 @@
/*
 * 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.plugins;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.opends.server.TestCaseUtils;
import org.opends.server.core.AddOperation;
import org.opends.server.core.ModifyOperation;
import org.opends.server.core.ModifyDNOperation;
import org.opends.server.tools.LDAPModify;
import static org.testng.Assert.*;
/**
 * This class defines a set of tests for the
 * org.opends.server.plugins.SevenBitCleanPlugin class.
 */
public class SevenBitCleanPluginTestCase
       extends PluginTestCase
{
  /**
   * The base64-encoded value that will be used as the password for entries that
   * are not 7-bit clean.
   */
  public static final String BASE64_DIRTY_PASSWORD = "cORzc3f2cmQ=";
  /**
   * Ensures that the Directory Server is running.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @BeforeClass()
  public void startServer()
         throws Exception
  {
    TestCaseUtils.startServer();
  }
  /**
   * Tests to ensure that it is possible to add a clean entry when the plugin
   * is disabled.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testAddCleanAllowedDisabled()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    String path = TestCaseUtils.createTempFile(
      "dn: uid=test.user,o=test",
      "changetype: add",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: test.user",
      "givenName: Test",
      "sn: User",
      "cn: Test User",
      "mail: test.user@example.com",
      "userPassword: password");
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "-f", path
    };
    assertEquals(LDAPModify.mainModify(args, false, System.out, System.err), 0);
  }
  /**
   * Tests to ensure that it is possible to add a dirty entry when the plugin
   * is disabled.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testAddDirtyAllowedDisabled()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    String path = TestCaseUtils.createTempFile(
      "dn: uid=test.user,o=test",
      "changetype: add",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: test.user",
      "givenName: Test",
      "sn: User",
      "cn: Test User",
      "mail: test.user@example.com",
      "userPassword:: " + BASE64_DIRTY_PASSWORD);
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "-f", path
    };
    assertEquals(LDAPModify.mainModify(args, false, System.out, System.err), 0);
  }
  /**
   * Tests to ensure that it is possible to add a clean entry when the plugin
   * is enabled.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testAddCleanAllowedEnabled()
         throws Exception
  {
    TestCaseUtils.dsconfig(
      "set-plugin-prop",
      "--plugin-name", "7-Bit Clean",
      "--set", "enabled:true");
    try
    {
      TestCaseUtils.initializeTestBackend(true);
      String path = TestCaseUtils.createTempFile(
        "dn: uid=test.user,o=test",
        "changetype: add",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "uid: test.user",
        "givenName: Test",
        "sn: User",
        "cn: Test User",
        "mail: test.user@example.com",
        "userPassword: password");
      String[] args =
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
        "-D", "cn=Directory Manager",
        "-w", "password",
        "-f", path
      };
      assertEquals(LDAPModify.mainModify(args, false, System.out, System.err),
                   0);
    }
    finally
    {
      TestCaseUtils.dsconfig(
        "set-plugin-prop",
        "--plugin-name", "7-Bit Clean",
        "--set", "enabled:false");
    }
  }
  /**
   * Tests to ensure that it is not possible to add a dirty entry when the
   * plugin is enabled.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testAddDirtyRejectedEnabled()
         throws Exception
  {
    TestCaseUtils.dsconfig(
      "set-plugin-prop",
      "--plugin-name", "7-Bit Clean",
      "--set", "enabled:true");
    try
    {
      TestCaseUtils.initializeTestBackend(true);
      String path = TestCaseUtils.createTempFile(
        "dn: uid=test.user,o=test",
        "changetype: add",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "uid: test.user",
        "givenName: Test",
        "sn: User",
        "cn: Test User",
        "mail: test.user@example.com",
        "userPassword:: " + BASE64_DIRTY_PASSWORD);
      String[] args =
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
        "-D", "cn=Directory Manager",
        "-w", "password",
        "-f", path
      };
      assertFalse(LDAPModify.mainModify(args, false, System.out, System.err) ==
                  0);
    }
    finally
    {
      TestCaseUtils.dsconfig(
        "set-plugin-prop",
        "--plugin-name", "7-Bit Clean",
        "--set", "enabled:false");
    }
  }
  /**
   * Tests to ensure that it is not possible to add a dirty entry when the
   * plugin is enabled but the entry being added is outside of the scope of the
   * plugin.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testAddDirtyAcceptedEnabledOutsideScope()
         throws Exception
  {
    TestCaseUtils.dsconfig(
      "set-plugin-prop",
      "--plugin-name", "7-Bit Clean",
      "--set", "enabled:true",
      "--set", "base-dn:dc=example,dc=com");
    try
    {
      TestCaseUtils.initializeTestBackend(true);
      String path = TestCaseUtils.createTempFile(
        "dn: uid=test.user,o=test",
        "changetype: add",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "uid: test.user",
        "givenName: Test",
        "sn: User",
        "cn: Test User",
        "mail: test.user@example.com",
        "userPassword:: " + BASE64_DIRTY_PASSWORD);
      String[] args =
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
        "-D", "cn=Directory Manager",
        "-w", "password",
        "-f", path
      };
      assertEquals(LDAPModify.mainModify(args, false, System.out, System.err),
                   0);
    }
    finally
    {
      TestCaseUtils.dsconfig(
        "set-plugin-prop",
        "--plugin-name", "7-Bit Clean",
        "--set", "enabled:false",
        "--remove", "base-dn:dc=example,dc=com");
    }
  }
  /**
   * Tests to ensure that it is possible to modify an entry to have a dirty
   * password value with the plugin disabled.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testModifyDirtyAllowedDisabled()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntry(
      "dn: uid=test.user,o=test",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: test.user",
      "givenName: Test",
      "sn: User",
      "cn: Test User",
      "mail: test.user@example.com",
      "userPassword: password");
    String path = TestCaseUtils.createTempFile(
      "dn: uid=test.user,o=test",
      "changetype: modify",
      "replace: userPassword",
      "userPassword:: " + BASE64_DIRTY_PASSWORD);
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "-f", path
    };
    assertEquals(LDAPModify.mainModify(args, false, System.out, System.err), 0);
  }
  /**
   * Tests to ensure that it is not possible to modify an entry to have a dirty
   * password value with the plugin enabled.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testModifyDirtyRejectedEnabled()
         throws Exception
  {
    TestCaseUtils.dsconfig(
      "set-plugin-prop",
      "--plugin-name", "7-Bit Clean",
      "--set", "enabled:true");
    try
    {
      TestCaseUtils.initializeTestBackend(true);
      TestCaseUtils.addEntry(
        "dn: uid=test.user,o=test",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "uid: test.user",
        "givenName: Test",
        "sn: User",
        "cn: Test User",
        "mail: test.user@example.com",
        "userPassword: password");
      String path = TestCaseUtils.createTempFile(
        "dn: uid=test.user,o=test",
        "changetype: modify",
        "replace: userPassword",
        "userPassword:: " + BASE64_DIRTY_PASSWORD);
      String[] args =
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
        "-D", "cn=Directory Manager",
        "-w", "password",
        "-f", path
      };
      assertFalse(LDAPModify.mainModify(args, false, System.out, System.err) ==
                  0);
    }
    finally
    {
      TestCaseUtils.dsconfig(
        "set-plugin-prop",
        "--plugin-name", "7-Bit Clean",
        "--set", "enabled:false");
    }
  }
  /**
   * Tests to ensure that it is possible to modify an entry containing a dirty
   * password value when changing that value to be clean.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testModifyDirtyToCleanAllowedEnabled()
         throws Exception
  {
      TestCaseUtils.initializeTestBackend(true);
      TestCaseUtils.addEntry(
        "dn: uid=test.user,o=test",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "uid: test.user",
        "givenName: Test",
        "sn: User",
        "cn: Test User",
        "mail: test.user@example.com",
        "userPassword:: " + BASE64_DIRTY_PASSWORD);
    TestCaseUtils.dsconfig(
      "set-plugin-prop",
      "--plugin-name", "7-Bit Clean",
      "--set", "enabled:true");
    try
    {
      String path = TestCaseUtils.createTempFile(
        "dn: uid=test.user,o=test",
        "changetype: modify",
        "replace: userPassword",
        "userPassword: clean");
      String[] args =
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
        "-D", "cn=Directory Manager",
        "-w", "password",
        "-f", path
      };
      assertEquals(LDAPModify.mainModify(args, false, System.out, System.err),
                   0);
    }
    finally
    {
      TestCaseUtils.dsconfig(
        "set-plugin-prop",
        "--plugin-name", "7-Bit Clean",
        "--set", "enabled:false");
    }
  }
  /**
   * Tests to ensure that it is possible to modify an entry containing a dirty
   * password in order to remove that value.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testModifyRemoveDirtyValueAllowedEnabled()
         throws Exception
  {
      TestCaseUtils.initializeTestBackend(true);
      TestCaseUtils.addEntry(
        "dn: uid=test.user,o=test",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "uid: test.user",
        "givenName: Test",
        "sn: User",
        "cn: Test User",
        "mail: test.user@example.com",
        "userPassword:: " + BASE64_DIRTY_PASSWORD);
    TestCaseUtils.dsconfig(
      "set-plugin-prop",
      "--plugin-name", "7-Bit Clean",
      "--set", "enabled:true");
    try
    {
      String path = TestCaseUtils.createTempFile(
        "dn: uid=test.user,o=test",
        "changetype: modify",
        "delete: userPassword",
        "userPassword:: " + BASE64_DIRTY_PASSWORD);
      String[] args =
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
        "-D", "cn=Directory Manager",
        "-w", "password",
        "-f", path
      };
      assertEquals(LDAPModify.mainModify(args, false, System.out, System.err),
                   0);
    }
    finally
    {
      TestCaseUtils.dsconfig(
        "set-plugin-prop",
        "--plugin-name", "7-Bit Clean",
        "--set", "enabled:false");
    }
  }
  /**
   * Tests to ensure that it is possible to perform a modify DN operation to
   * provide a dirty new RDN with the plugin disabled.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testModifyDNDirtyAllowedDisabled()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntry(
      "dn: uid=test.user,o=test",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: test.user",
      "givenName: Test",
      "sn: User",
      "cn: Test User",
      "mail: test.user@example.com",
      "userPassword: password");
    String path = TestCaseUtils.createTempFile(
      "dn: uid=test.user,o=test",
      "changetype: modrdn",
      "newrdn: uid=p\\e4ssw\\f6rd",
      "deleteoldrdn: 1");
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "-f", path
    };
    assertEquals(LDAPModify.mainModify(args, false, System.out, System.err), 0);
  }
  /**
   * Tests to ensure that it is not possible to perform a modify DN operation to
   * provide a dirty new RDN with the plugin enabled.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testModifyDNDirtyRejectedEnabled()
         throws Exception
  {
    TestCaseUtils.dsconfig(
      "set-plugin-prop",
      "--plugin-name", "7-Bit Clean",
      "--set", "enabled:true");
    try
    {
      TestCaseUtils.initializeTestBackend(true);
      TestCaseUtils.addEntry(
        "dn: uid=test.user,o=test",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "uid: test.user",
        "givenName: Test",
        "sn: User",
        "cn: Test User",
        "mail: test.user@example.com",
        "userPassword: password");
      String path = TestCaseUtils.createTempFile(
        "dn: uid=test.user,o=test",
        "changetype: modrdn",
        "newrdn: uid=p\\e4ssw\\f6rd",
        "deleteoldrdn: 1");
      String[] args =
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
        "-D", "cn=Directory Manager",
        "-w", "password",
        "-f", path
      };
      assertFalse(LDAPModify.mainModify(args, false, System.out, System.err) ==
                  0);
    }
    finally
    {
      TestCaseUtils.dsconfig(
        "set-plugin-prop",
        "--plugin-name", "7-Bit Clean",
        "--set", "enabled:false");
    }
  }
  /**
   * Tests to ensure that it is possible to perform a modify DN operation to
   * provide a clean new RDN for a dirty entry with the plugin enabled.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testModifyDNDirtyToCleanAllowedEnabled()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntry(
      "dn: uid=p\\e4ssw\\f6rd,o=test",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid:: " + BASE64_DIRTY_PASSWORD,
      "givenName: Test",
      "sn: User",
      "cn: Test User",
      "mail: test.user@example.com",
      "userPassword: password");
    TestCaseUtils.dsconfig(
      "set-plugin-prop",
      "--plugin-name", "7-Bit Clean",
      "--set", "enabled:true");
    try
    {
      String path = TestCaseUtils.createTempFile(
        "dn: uid=p\\e4ssw\\f6rd,o=test",
        "changetype: modrdn",
        "newrdn: uid=test.user",
        "deleteoldrdn: 1");
      String[] args =
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
        "-D", "cn=Directory Manager",
        "-w", "password",
        "-f", path
      };
      assertEquals(LDAPModify.mainModify(args, false, System.out, System.err),
                   0);
    }
    finally
    {
      TestCaseUtils.dsconfig(
        "set-plugin-prop",
        "--plugin-name", "7-Bit Clean",
        "--set", "enabled:false");
    }
  }
}