From 26150e5cc7b18069cfabaa916fa19416d378de70 Mon Sep 17 00:00:00 2001
From: dugan <dugan@localhost>
Date: Tue, 21 Aug 2007 13:31:07 +0000
Subject: [PATCH] Add attribute uniqueness plugin implementation that provides single-server attribute uniqueness. The plugin has the following features:
---
opends/resource/schema/02-config.ldif | 12
opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/UniqueAttributePluginTestCase.java | 912 +++++++++++++++++++++++++++++++++
opends/src/server/org/opends/server/plugins/UniqueAttributePlugin.java | 485 +++++++++++++++++
opends/resource/config/config.ldif | 12
opends/src/admin/defn/org/opends/server/admin/std/UniqueAttributePluginConfiguration.xml | 118 ++++
opends/src/messages/messages/plugin.properties | 12
opends/tests/unit-tests-testng/resource/config-changes.ldif | 19
7 files changed, 1,569 insertions(+), 1 deletions(-)
diff --git a/opends/resource/config/config.ldif b/opends/resource/config/config.ldif
index 6315958..978d667 100644
--- a/opends/resource/config/config.ldif
+++ b/opends/resource/config/config.ldif
@@ -1375,6 +1375,18 @@
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=Root DNs,cn=config
objectClass: top
objectClass: ds-cfg-root-dn-base
diff --git a/opends/resource/schema/02-config.ldif b/opends/resource/schema/02-config.ldif
index 87d0a8e..af1d21d 100644
--- a/opends/resource/schema/02-config.ldif
+++ b/opends/resource/schema/02-config.ldif
@@ -1567,6 +1567,12 @@
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.470 NAME 'ds-cfg-replace-pattern'
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE
X-ORIGIN 'OpenDS Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.26027.1.1.467
+ NAME 'ds-cfg-unique-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.468
+ NAME 'ds-cfg-unique-attribute-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 )
@@ -2201,4 +2207,8 @@
STRUCTURAL MUST ( ds-cfg-match-attribute $ ds-cfg-match-pattern )
MAY ( ds-cfg-match-base-dn $ ds-cfg-replace-pattern )
X-ORIGIN 'OpenDS Directory Server' )
-
+objectClasses: ( 1.3.6.1.4.1.26027.1.2.120
+ NAME 'ds-cfg-unique-attribute-plugin' SUP ds-cfg-plugin
+ STRUCTURAL
+ MAY ( ds-cfg-unique-attribute-type $ ds-cfg-unique-attribute-base-dn )
+ X-ORIGIN 'OpenDS Directory Server' )
diff --git a/opends/src/admin/defn/org/opends/server/admin/std/UniqueAttributePluginConfiguration.xml b/opends/src/admin/defn/org/opends/server/admin/std/UniqueAttributePluginConfiguration.xml
new file mode 100644
index 0000000..58c453c
--- /dev/null
+++ b/opends/src/admin/defn/org/opends/server/admin/std/UniqueAttributePluginConfiguration.xml
@@ -0,0 +1,118 @@
+<?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="unique-attribute-plugin"
+ plural-name="unique-attribute-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 />
+ is used enforce constraints on the value of an attribute within a portion
+ of the directory.
+ </adm:synopsis>
+
+ <adm:profile name="ldap">
+ <ldap:object-class>
+ <ldap:oid>1.3.6.1.4.1.26027.1.2.120</ldap:oid>
+ <ldap:name>ds-cfg-unique-attribute-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.UniqueAttributePlugin
+ </adm:value>
+ </adm:defined>
+ </adm:default-behavior>
+ </adm:property-override>
+
+ <adm:property name="unique-attribute-type" mandatory="false"
+ multi-valued="true">
+ <adm:synopsis>
+ Specifies the attribute type to check for value uniqueness.
+ </adm:synopsis>
+ <adm:description>
+ Specifies the attribute type to check for value uniqueness. The
+ values for each ds-cfg-unique-attribute-type attribute must be unique
+ within each base DN specified in the configuration's
+ ds-cfg-unique-attribute-base-dn attribute or within all of the server's
+ public naming contexts if no base DNs were specified in the configuration.
+ </adm:description>
+ <adm:default-behavior>
+ <adm:alias>
+ <adm:synopsis>
+ The plugin will bypass unique attribute checking.
+ </adm:synopsis>
+ </adm:alias>
+ </adm:default-behavior>
+ <adm:syntax>
+ <adm:string />
+ </adm:syntax>
+ <adm:profile name="ldap">
+ <ldap:attribute>
+ <ldap:oid>1.3.6.1.4.1.26027.1.1.467</ldap:oid>
+ <ldap:name>ds-cfg-unique-attribute-type</ldap:name>
+ </ldap:attribute>
+ </adm:profile>
+ </adm:property>
+
+ <adm:property name="unique-attribute-base-dn" mandatory="false"
+ multi-valued="true">
+ <adm:synopsis>
+ Specifies a base DN that the attribute must be unique within.
+ </adm:synopsis>
+ <adm:description>
+ Specifies a base DN that the attribute must be unique within.
+ </adm:description>
+ <adm:default-behavior>
+ <adm:alias>
+ <adm:synopsis>
+ The plugin will use the server's public naming contexts in the
+ searches.
+ </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.468</ldap:oid>
+ <ldap:name>ds-cfg-unique-attribute-base-dn</ldap:name>
+ </ldap:attribute>
+ </adm:profile>
+ </adm:property>
+
+</adm:managed-object>
+
diff --git a/opends/src/messages/messages/plugin.properties b/opends/src/messages/messages/plugin.properties
index 8b7e6d2..a669f5c 100644
--- a/opends/src/messages/messages/plugin.properties
+++ b/opends/src/messages/messages/plugin.properties
@@ -303,3 +303,15 @@
subordinate modify DN plugin defined in configuration entry %s returned null \
when invoked for connection %d operation %s. This is an illegal response, \
and processing on this operation will be terminated
+SEVERE_ERR_PLUGIN_UNIQUEATTR_INVALID_PLUGIN_TYPE_77=An attempt was made to \
+ register the Unique Attribute plugin to be invoked as a %s plugin. This \
+ plugin type is not allowed for this plugin
+SEVERE_ERR_PLUGIN_UNIQUEATTR_MOD_NOT_UNIQUE_78=An error occurred while \
+ attempting to modify an attribute value of entry %s because the proposed \
+ changes failed the attribute value uniqueness check
+SEVERE_ERR_PLUGIN_UNIQUEATTR_ADD_NOT_UNIQUE_79=An error occurred while \
+ attempting to add the entry %s because one of the entry's attribute values \
+ failed the attribute value uniqueness check
+SEVERE_ERR_PLUGIN_UNIQUEATTR_MODDN_NOT_UNIQUE_80=An error occurred while \
+ attempting to perform a modify DN operation on entry %s because the proposed \
+ changes failed the attribute value uniqueness check
diff --git a/opends/src/server/org/opends/server/plugins/UniqueAttributePlugin.java b/opends/src/server/org/opends/server/plugins/UniqueAttributePlugin.java
new file mode 100644
index 0000000..d9b169c
--- /dev/null
+++ b/opends/src/server/org/opends/server/plugins/UniqueAttributePlugin.java
@@ -0,0 +1,485 @@
+/*
+ * 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.opends.server.admin.std.server.UniqueAttributePluginCfg;
+import org.opends.server.admin.std.meta.PluginCfgDefn;
+import org.opends.server.admin.server.ConfigurationChangeListener;
+import org.opends.server.api.plugin.DirectoryServerPlugin;
+import org.opends.server.api.plugin.PluginType;
+import org.opends.server.api.plugin.PreOperationPluginResult;
+import org.opends.server.config.ConfigException;
+import org.opends.server.types.*;
+import org.opends.server.types.operation.PreOperationAddOperation;
+import org.opends.server.types.operation.PreOperationModifyDNOperation;
+import org.opends.server.types.operation.PreOperationModifyOperation;
+import org.opends.server.types.operation.PreOperationOperation;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.protocols.internal.InternalSearchOperation;
+import org.opends.messages.Message;
+import static org.opends.messages.PluginMessages.*;
+
+import java.util.*;
+
+/**
+ * This class implements a Directory Server plugin that performs attribute
+ * uniqueness checking on the add, modify and modifyDN operations. If the
+ * operation is eligible for checking based on a set of configuration criteria,
+ * then the operation's attribute values will be checked, using that
+ * configuration criteria, for uniqueness against the server's values to
+ * determine if the operation can proceed.
+ */
+public class UniqueAttributePlugin
+ extends DirectoryServerPlugin<UniqueAttributePluginCfg>
+ implements ConfigurationChangeListener<UniqueAttributePluginCfg> {
+
+ //Current plugin configuration.
+ private UniqueAttributePluginCfg currentConfiguration;
+
+ //List of attribute types that must be unique.
+ private LinkedHashSet<AttributeType> uniqueAttributeTypes =
+ new LinkedHashSet<AttributeType>();
+
+//List of base DNs that limit the scope of the uniqueness checking.
+ private LinkedHashSet<DN> baseDNs = new LinkedHashSet<DN>();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public final void initializePlugin(Set<PluginType> pluginTypes,
+ UniqueAttributePluginCfg configuration)
+ throws ConfigException {
+ configuration.addUniqueAttributeChangeListener(this);
+ currentConfiguration = configuration;
+ for (PluginType t : pluginTypes)
+ switch (t) {
+ case PRE_OPERATION_ADD:
+ case PRE_OPERATION_MODIFY:
+ case PRE_OPERATION_MODIFY_DN:
+ // These are acceptable.
+ break;
+ default:
+ Message message =
+ ERR_PLUGIN_UNIQUEATTR_INVALID_PLUGIN_TYPE.get(t.toString());
+ throw new ConfigException(message);
+
+ }
+ //Load base DNs if any.
+ for(DN baseDN : configuration.getUniqueAttributeBaseDN())
+ baseDNs.add(baseDN);
+ //Load attribute types if any.
+ for(String attributeType : configuration.getUniqueAttributeType()) {
+ AttributeType type =
+ DirectoryServer.getAttributeType(attributeType.toLowerCase());
+ if(type == null)
+ type =
+ DirectoryServer.getDefaultAttributeType(attributeType.toLowerCase());
+ uniqueAttributeTypes.add(type);
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+// @Override()
+ public boolean
+ isConfigurationAcceptable(UniqueAttributePluginCfg configuration,
+ List<Message> unacceptableReasons) {
+ return isConfigurationChangeAcceptable(configuration, unacceptableReasons);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isConfigurationChangeAcceptable(
+ UniqueAttributePluginCfg configuration,
+ List<Message> unacceptableReasons) {
+ boolean configAcceptable = true;
+ for (PluginCfgDefn.PluginType pluginType : configuration.getPluginType())
+ {
+ switch (pluginType)
+ {
+ case PREOPERATIONADD:
+ case PREOPERATIONMODIFY:
+ case PREOPERATIONMODIFYDN:
+ // These are acceptable.
+ break;
+
+ default:
+ Message message =
+ ERR_PLUGIN_UNIQUEATTR_INVALID_PLUGIN_TYPE.get(pluginType.toString());
+ unacceptableReasons.add(message);
+ configAcceptable = false;
+ }
+ }
+ return configAcceptable;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public ConfigChangeResult applyConfigurationChange(
+ UniqueAttributePluginCfg newConfiguration) {
+ ResultCode resultCode = ResultCode.SUCCESS;
+ boolean adminActionRequired = false;
+ ArrayList<Message> messages = new ArrayList<Message>();
+ LinkedHashSet<AttributeType> newUniqueattributeTypes=
+ new LinkedHashSet<AttributeType>();
+ LinkedHashSet<DN> newConfiguredBaseDNs = new LinkedHashSet<DN>();
+ //Load base DNs from new configuration.
+ for(DN baseDN : newConfiguration.getUniqueAttributeBaseDN())
+ newConfiguredBaseDNs.add(baseDN);
+ //Load attribute types from new configuration.
+ for(String attributeType : newConfiguration.getUniqueAttributeType()) {
+ AttributeType type =
+ DirectoryServer.getAttributeType(attributeType.toLowerCase());
+ if(type == null)
+ type =
+ DirectoryServer.getDefaultAttributeType(attributeType.toLowerCase());
+ newUniqueattributeTypes.add(type);
+ }
+ //Switch to the new lists and configurations.
+ baseDNs = newConfiguredBaseDNs;
+ uniqueAttributeTypes = newUniqueattributeTypes;
+ currentConfiguration = newConfiguration;
+ return new ConfigChangeResult(resultCode, adminActionRequired, messages);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public final PreOperationPluginResult
+ doPreOperation(PreOperationAddOperation addOperation) {
+ PreOperationPluginResult pluginResult=PreOperationPluginResult.SUCCESS;
+ DN entryDN=addOperation.getEntryDN();
+ if(isEntryUniquenessCandidate(entryDN)) {
+ List<AttributeValue> valueList =
+ getEntryAttributeValues(addOperation.getEntryToAdd());
+ if(!searchAllBaseDNs(valueList, entryDN))
+ pluginResult = getPluginErrorResult(addOperation,
+ ERR_PLUGIN_UNIQUEATTR_ADD_NOT_UNIQUE.get(entryDN.toString()));
+ }
+ return pluginResult;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public final PreOperationPluginResult
+ doPreOperation(PreOperationModifyOperation modifyOperation) {
+ PreOperationPluginResult pluginResult=PreOperationPluginResult.SUCCESS;
+ DN entryDN = modifyOperation.getEntryDN();
+ if(isEntryUniquenessCandidate(entryDN)) {
+ List<AttributeValue> valueList =
+ getModificationAttributeValues(modifyOperation.getModifications(),
+ modifyOperation.getModifiedEntry());
+ if(!searchAllBaseDNs(valueList, entryDN))
+ pluginResult = getPluginErrorResult(modifyOperation,
+ ERR_PLUGIN_UNIQUEATTR_MOD_NOT_UNIQUE.get(entryDN.toString()));
+ }
+ return pluginResult;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public final PreOperationPluginResult
+ doPreOperation(PreOperationModifyDNOperation modifyDNOperation) {
+ PreOperationPluginResult pluginResult=PreOperationPluginResult.SUCCESS;
+ DN entryDN=modifyDNOperation.getOriginalEntry().getDN();
+ //If the operation has a new superior DN then use that, since any moves
+ //need to make sure there are no conflicts in the new superior base DN.
+ if(modifyDNOperation.getNewSuperior() != null)
+ entryDN = modifyDNOperation.getNewSuperior();
+ if(isEntryUniquenessCandidate(entryDN)) {
+ List<AttributeValue> valueList =
+ getRDNAttributeValues(modifyDNOperation.getNewRDN());
+ if(!searchAllBaseDNs(valueList, entryDN))
+ pluginResult = getPluginErrorResult(modifyDNOperation,
+ ERR_PLUGIN_UNIQUEATTR_MODDN_NOT_UNIQUE.get(entryDN.toString()));
+ }
+ return pluginResult;
+ }
+
+ /**
+ * Determine if the specified DN is a candidate for attribute uniqueness
+ * checking. Checking is skipped if the the unique attribute type list is
+ * empty or if there are base DNS configured and the specified DN is not a
+ * descendant of any of them. Checking is performed for all other cases.
+ *
+ * @param dn The DN to check.
+ *
+ * @return Returns <code>true</code> if the operation needs uniqueness
+ * checking performed.
+ */
+ private boolean
+ isEntryUniquenessCandidate(DN dn) {
+ if(uniqueAttributeTypes.isEmpty())
+ return false;
+ else if(baseDNs.isEmpty())
+ return true;
+ else {
+ for(DN baseDN : baseDNs)
+ if(baseDN.isAncestorOf(dn))
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns a plugin result instance indicating that the operation should be
+ * terminated; that no further pre-operation processing should be performed
+ * and that the server should send the response immediately. It also adds
+ * a CONSTRAINT_VIOLATION result code and the specified error message to
+ * the specified operation.
+ *
+ * @param operation The operation to add the result code and message to.
+ *
+ * @param message The message to add to the operation.
+ *
+ * @return Returns a plugin result instance that halts further processing
+ * on this operation.
+ */
+ private PreOperationPluginResult
+ getPluginErrorResult(PreOperationOperation operation, Message message) {
+ operation.appendErrorMessage(message);
+ operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+ return new PreOperationPluginResult(false, false, true);
+ }
+
+ /**
+ * Searches all of the the attribute types of the specified RDN for matches
+ * in the unique attribute type list. If matches are found, then the
+ * corresponding values are added to a list of values that will be eventually
+ * searched for uniqueness.
+
+ * @param rdn The RDN to examine.
+ *
+ * @return Returns a list of attribute values from the RDN that matches the
+ * unique attribute type list.
+ */
+ private List<AttributeValue> getRDNAttributeValues(RDN rdn) {
+ LinkedList<AttributeValue> valueList=
+ new LinkedList<AttributeValue>();
+ int numAVAs = rdn.getNumValues();
+ for (int i = 0; i < numAVAs; i++) {
+ if(uniqueAttributeTypes.contains(rdn.getAttributeType(i)))
+ valueList.add(rdn.getAttributeValue(i));
+ }
+ return valueList;
+ }
+
+ /**
+ * Searches all of the attribute types of the specified entry for matches
+ * in the unique attribute type list. Ff matches are found, then the
+ * corresponding values are added to a list of values that will eventually
+ * be searched for uniqueness.
+ *
+ * @param entry The entry to examine.
+ *
+ * @return Returns a list of attribute values from the entry that matches the
+ * unique attribute type list.
+ */
+ private List<AttributeValue> getEntryAttributeValues(Entry entry) {
+ LinkedList<AttributeValue> valueList=new LinkedList<AttributeValue>();
+ for(AttributeType attributeType : uniqueAttributeTypes) {
+ if(entry.hasAttribute(attributeType)) {
+ List<Attribute> attrList=entry.getAttribute(attributeType);
+ for (Attribute a : attrList)
+ valueList.addAll(a.getValues());
+ }
+ }
+ return valueList;
+ }
+
+ /**
+ * Iterate over the unique attribute type list calling a method that will
+ * search the specified modification list for each attribute type and add
+ * the corresponding values to a list of values.
+ *
+ * @param modificationList The modification list to search over.
+ *
+ * @param modifedEntry The copy of the entry with modifications applied.
+ *
+ * @return Returns a list of attribute values from the modification list
+ * that matches the unique attribute type list.
+ */
+ private List<AttributeValue>
+ getModificationAttributeValues(List<Modification> modificationList,
+ Entry modifedEntry) {
+ LinkedList<AttributeValue> valueList =
+ new LinkedList<AttributeValue>();
+ for(AttributeType attributeType : uniqueAttributeTypes)
+ getModValuesForAttribute(modificationList, attributeType, valueList,
+ modifedEntry);
+ return valueList;
+ }
+
+ /**
+ * Searches the specified modification list for the provided attribute type.
+ * If a match is found than the attribute value is added to a list of
+ * attribute values that will be eventually searched for uniqueness.
+ *
+ * @param modificationList The modification list to search over.
+ *
+ * @param attributeType The attribute type to search for.
+ *
+ * @param valueList A list of attribute values to put the values in.
+ *
+ * @param modifiedEntry A copy of the entry with modifications applied.
+ */
+ private void
+ getModValuesForAttribute(List<Modification> modificationList,
+ AttributeType attributeType,
+ LinkedList<AttributeValue> valueList,
+ Entry modifiedEntry) {
+
+ for(Modification modification : modificationList) {
+ ModificationType modType=modification.getModificationType();
+ //Skip delete modifications or modifications on attribute types not
+ //matching the specified type.
+ if(modType == ModificationType.DELETE ||
+ !modification.getAttribute().getAttributeType().equals(attributeType))
+ continue;
+ //Increment uses modified entry to get value for the attribute type.
+ if(modType == ModificationType.INCREMENT) {
+ List<Attribute> modifiedAttrs =
+ modifiedEntry.getAttribute(attributeType,
+ modification.getAttribute().getOptions());
+ if (modifiedAttrs != null) {
+ for (Attribute a : modifiedAttrs)
+ valueList.addAll(a.getValues());
+ }
+ } else {
+ Attribute modifiedAttribute=modification.getAttribute();
+ if(modifiedAttribute.hasValue())
+ valueList.addAll(modifiedAttribute.getValues());
+ }
+ }
+ }
+
+
+ /**
+ * Iterates over the base DNs configured by the plugin entry searching for
+ * value matches. If the base DN list is empty then the public naming
+ * contexts are used instead.
+ *
+ * @param valueList The list of values to search for.
+ *
+ * @param entryDN The DN of the entry related to the operation.
+ *
+ * @return Returns <code>true</code> if a value is unique.
+ */
+ private boolean
+ searchAllBaseDNs(List<AttributeValue> valueList, DN entryDN) {
+ if(valueList.isEmpty())
+ return true;
+ if(baseDNs.isEmpty()) {
+ for(DN baseDN : DirectoryServer.getPublicNamingContexts().keySet()) {
+ if(searchBaseDN(valueList, baseDN, entryDN))
+ return false;
+ }
+ } else {
+ for(DN baseDN : baseDNs) {
+ if(searchBaseDN(valueList, baseDN, entryDN))
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Search a single base DN for all the values in a specified value list.
+ * A filter is created to search all the attribute at once for each
+ * value in the list.
+ *
+ * @param valueList The list of values to search for.
+ *
+ * @param baseDN The base DN to base the search at.
+ *
+ * @param entryDN The DN of the entry related to the operation.
+ *
+ * @return Returns <code>true</code> if the values are not unique under the
+ * under the base DN.
+ */
+ private boolean
+ searchBaseDN(List<AttributeValue> valueList, DN baseDN,
+ DN entryDN) {
+ //Filter set to hold component filters.
+ HashSet<SearchFilter> componentFilters=new HashSet<SearchFilter>();
+ for(AttributeValue value : valueList) {
+ //Iterate over the unique attribute list and build a equality filter
+ //using each attribute type in the list and the current value being
+ //matched.
+ for(AttributeType attributeType : uniqueAttributeTypes)
+ componentFilters.add(SearchFilter.createEqualityFilter(attributeType,
+ value));
+ //Perform the search using the OR filter created from the filter
+ //components created above.
+ InternalClientConnection conn =
+ InternalClientConnection.getRootConnection();
+ InternalSearchOperation operation = conn.processSearch(baseDN,
+ SearchScope.WHOLE_SUBTREE,
+ DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, true,
+ SearchFilter.createORFilter(componentFilters),
+ null);
+ switch (operation.getResultCode()) {
+ case SUCCESS:
+ break;
+
+ case NO_SUCH_OBJECT:
+ //This base DN doesn't exist, return false.
+ return false;
+
+ case SIZE_LIMIT_EXCEEDED:
+ case TIME_LIMIT_EXCEEDED:
+ case ADMIN_LIMIT_EXCEEDED:
+ default:
+ //Couldn't determine if the attribute is unique because an
+ //administrative limit was reached during the search. Fail the
+ //operation by returning true. Possibly log an error here?
+ return true;
+ }
+ for (SearchResultEntry entry : operation.getSearchEntries()) {
+ //Only allow the entry DN to exist. The user might be modifying
+ //the attribute values and putting the same value back. Any other entry
+ //means the value is not unique.
+ if(!entry.getDN().equals(entryDN))
+ return true;
+ }
+ componentFilters.clear();
+ }
+ return false;
+ }
+}
diff --git a/opends/tests/unit-tests-testng/resource/config-changes.ldif b/opends/tests/unit-tests-testng/resource/config-changes.ldif
index d7dda3e..1ffd43f 100644
--- a/opends/tests/unit-tests-testng/resource/config-changes.ldif
+++ b/opends/tests/unit-tests-testng/resource/config-changes.ldif
@@ -301,6 +301,25 @@
ds-cfg-plugin-type: preOperationAdd
ds-cfg-plugin-type: preOperationModify
+dn: cn=UID Unique Attribute ,cn=Plugins,cn=config
+changeType: modify
+replace: ds-cfg-plugin-enabled
+ds-cfg-plugin-enabled: true
+-
+replace: ds-cfg-unique-attribute-type
+
+dn: cn=Test Unique Attribute,cn=Plugins,cn=config
+changeType: add
+objectClass: top
+objectClass: ds-cfg-plugin
+objectClass: ds-cfg-unique-attribute-plugin
+cn: Test Unique Attribute
+ds-cfg-plugin-class: org.opends.server.plugins.UniqueAttributePlugin
+ds-cfg-plugin-enabled: true
+ds-cfg-plugin-type: preOperationAdd
+ds-cfg-plugin-type: preOperationModify
+ds-cfg-plugin-type: preOperationModifyDN
+
dn: cn=JKS,cn=Key Manager Providers,cn=config
changetype: modify
replace: ds-cfg-key-manager-provider-enabled
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/UniqueAttributePluginTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/UniqueAttributePluginTestCase.java
new file mode 100644
index 0000000..28d401a
--- /dev/null
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/UniqueAttributePluginTestCase.java
@@ -0,0 +1,912 @@
+/*
+ * 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.*;
+import static org.testng.Assert.assertEquals;
+import org.opends.server.types.*;
+import org.opends.server.TestCaseUtils;
+import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.AddOperation;
+import org.opends.server.core.ModifyDNOperation;
+import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.config.ConfigException;
+import org.opends.server.admin.std.server.UniqueAttributePluginCfg;
+import org.opends.server.admin.std.meta.UniqueAttributePluginCfgDefn;
+import org.opends.server.admin.server.AdminTestCaseUtils;
+import org.opends.server.api.plugin.PluginType;
+
+import java.util.List;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.LinkedHashSet;
+
+
+/**
+ * Unit test to test the unique attribute plugin.
+ */
+
+public class UniqueAttributePluginTestCase extends PluginTestCase {
+
+ private DN uidConfigDN;
+ private DN testConfigDN;
+ private String dsConfigAttrType="ds-cfg-unique-attribute-type";
+ private String dsConfigBaseDN="ds-cfg-unique-attribute-base-dn";
+
+ /**
+ * Ensures that the Directory Server is running.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @BeforeClass()
+ public void startServer()
+ throws Exception
+ {
+ TestCaseUtils.startServer();
+ //Add entries to two backends to test public naming context.
+ addTestEntries("o=test", 't');
+ TestCaseUtils.clearJEBackend(true,"userRoot", "dc=example,dc=com");
+ addTestEntries("dc=example,dc=com", 'x');
+ uidConfigDN=DN.decode("cn=UID Unique Attribute ,cn=Plugins,cn=config");
+ testConfigDN=DN.decode("cn=Test Unique Attribute,cn=Plugins,cn=config");
+ }
+
+
+ /**
+ * Clears configuration information before each method run.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @BeforeMethod
+ public void clearConfigEntries() throws Exception {
+ deleteAttrsFromEntry(uidConfigDN,dsConfigAttrType, dsConfigBaseDN);
+ deleteAttrsFromEntry(testConfigDN,dsConfigAttrType, dsConfigBaseDN);
+ }
+
+
+ /**
+ * Clears things up after the unit test is completed.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @AfterClass
+ public void tearDown() throws Exception {
+ clearConfigEntries();
+ TestCaseUtils.clearJEBackend(false,"userRoot", "dc=example,dc=com");
+ }
+
+
+ /**
+ * Retrieves a set of valid configuration entries that may be used to
+ * initialize the plugin.
+ *
+ * @return An array of config entries.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @DataProvider(name = "validConfigs")
+ public Object[][] getValidConfigs()
+ throws Exception
+ {
+ List<Entry> entries = TestCaseUtils.makeEntries(
+ "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: true",
+ "ds-cfg-plugin-type: preOperationAdd",
+ "ds-cfg-plugin-type: preOperationModify",
+ "ds-cfg-plugin-type: preOperationModifyDN",
+ "",
+ "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: true",
+ "ds-cfg-plugin-type: preOperationAdd",
+ "ds-cfg-plugin-type: preOperationModify",
+ "ds-cfg-plugin-type: preOperationModifyDN",
+ "",
+ "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: true",
+ "ds-cfg-plugin-type: preOperationAdd",
+ "ds-cfg-plugin-type: preOperationModify",
+ "ds-cfg-plugin-type: preOperationModifyDN",
+ "ds-cfg-unique-attribute-type: uid",
+ "",
+ "dn: cn=mail Unique Attribute,cn=Plugins,cn=config",
+ "objectClass: top",
+ "objectClass: ds-cfg-plugin",
+ "objectClass: ds-cfg-unique-attribute-plugin",
+ "cn: mail Unique Attribute",
+ "ds-cfg-plugin-class: org.opends.server.plugins.UniqueAttributePlugin",
+ "ds-cfg-plugin-enabled: true",
+ "ds-cfg-plugin-type: preOperationAdd",
+ "ds-cfg-plugin-type: preOperationModify",
+ "ds-cfg-plugin-type: preOperationModifyDN",
+ "ds-cfg-unique-attribute-type: mail",
+ "ds-cfg-unique-attribute-type: othermail",
+ "",
+ "dn: cn=phone Unique Attribute,cn=Plugins,cn=config",
+ "objectClass: top",
+ "objectClass: ds-cfg-plugin",
+ "objectClass: ds-cfg-unique-attribute-plugin",
+ "cn: phone Unique Attribute",
+ "ds-cfg-plugin-class: org.opends.server.plugins.UniqueAttributePlugin",
+ "ds-cfg-plugin-enabled: true",
+ "ds-cfg-plugin-type: preOperationAdd",
+ "ds-cfg-plugin-type: preOperationModify",
+ "ds-cfg-plugin-type: preOperationModifyDN",
+ "ds-cfg-unique-attribute-type: telephone",
+ "ds-cfg-unique-attribute-type: mobile",
+ "ds-cfg-unique-attribute-type: fax",
+ "ds-cfg-unique-attribute-base-dn: dc=example,dc=com",
+ "ds-cfg-unique-attribute-base-dn: o=test",
+ "",
+ "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: true",
+ "ds-cfg-plugin-type: preOperationAdd",
+ "ds-cfg-plugin-type: preOperationModify",
+ "ds-cfg-plugin-type: preOperationModifyDN",
+ "ds-cfg-unique-attribute-type: uid",
+ "",
+ "dn: cn=UID Unique Attribute,cn=Plugins,cn=config",
+ "objectClass: top",
+ "objectClass: ds-cfg-plugin",
+ "objectClass: ds-cfg-unique-attribute-plugin",
+ "cn: UUID Unique Attribute",
+ "ds-cfg-plugin-class: org.opends.server.plugins.UniqueAttributePlugin",
+ "ds-cfg-plugin-enabled: true",
+ "ds-cfg-plugin-type: preOperationAdd",
+ "",
+ "dn: cn=UID Unique Attribute,cn=Plugins,cn=config",
+ "objectClass: top",
+ "objectClass: ds-cfg-plugin",
+ "objectClass: ds-cfg-unique-attribute-plugin",
+ "cn: UUID Unique Attribute",
+ "ds-cfg-plugin-class: org.opends.server.plugins.UniqueAttributePlugin",
+ "ds-cfg-plugin-enabled: true",
+ "ds-cfg-plugin-type: preOperationModify",
+ "",
+ "dn: cn=UID Unique Attribute,cn=Plugins,cn=config",
+ "objectClass: top",
+ "objectClass: ds-cfg-plugin",
+ "objectClass: ds-cfg-unique-attribute-plugin",
+ "cn: UUID Unique Attribute",
+ "ds-cfg-plugin-class: org.opends.server.plugins.UniqueAttributePlugin",
+ "ds-cfg-plugin-enabled: true",
+ "ds-cfg-plugin-type: preOperationModifyDN");
+ Object[][] array = new Object[entries.size()][1];
+ for (int i=0; i < array.length; i++)
+ {
+ array[i] = new Object[] { entries.get(i) };
+ }
+
+ return array;
+ }
+
+
+ /**
+ * Tests the process of initializing the server with valid configurations.
+ *
+ * @param e The configuration entry to use for the initialization.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test(dataProvider = "validConfigs")
+ public void testInitializeWithValidConfigs(Entry e)
+ throws Exception
+ {
+ HashSet<PluginType> pluginTypes = new HashSet<PluginType>();
+ List<Attribute> attrList = e.getAttribute("ds-cfg-plugin-type");
+ for (Attribute a : attrList){
+ for (AttributeValue v : a.getValues())
+ pluginTypes.add(PluginType.forName(v.getStringValue().toLowerCase()));
+ }
+ UniqueAttributePluginCfg configuration =
+ AdminTestCaseUtils.getConfiguration(
+ UniqueAttributePluginCfgDefn.getInstance(), e);
+
+ UniqueAttributePlugin plugin = new UniqueAttributePlugin();
+ plugin.initializePlugin(pluginTypes, configuration);
+ plugin.finalizePlugin();
+ }
+
+ /**
+ * Retrieves a set of valid configuration entries that may be used to
+ * initialize the plugin.
+
+ * @return An array of config entries.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @DataProvider(name = "invalidConfigs")
+ public Object[][] getInValidConfigs()
+ throws Exception
+ {
+ List<Entry> entries = TestCaseUtils.makeEntries(
+ "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: true",
+ "ds-cfg-plugin-type: preOperationAdd",
+ "ds-cfg-plugin-type: preOperationModify",
+ "ds-cfg-plugin-type: ldifImport",
+ "",
+ "dn: cn=phone Unique Attribute,cn=Plugins,cn=config",
+ "objectClass: top",
+ "objectClass: ds-cfg-plugin",
+ "cn: phone Unique Attribute",
+ "ds-cfg-plugin-class: org.opends.server.plugins.UniqueAttributePlugin",
+ "ds-cfg-plugin-enabled: true",
+ "ds-cfg-plugin-type: preOperationAdd",
+ "ds-cfg-plugin-type: preOperationModify",
+ "ds-cfg-plugin-type: preOperationModifyDN",
+ "ds-cfg-unique-attribute-type: telephone",
+ "ds-cfg-unique-attribute-type: mobile",
+ "ds-cfg-unique-attribute-type: fax",
+ "ds-cfg-unique-attribute-base-dn: dc=example,dc=com",
+ "",
+ "dn: cn=phone Unique Attribute,cn=Plugins,cn=config",
+ "objectClass: top",
+ "objectClass: ds-cfg-plugin",
+ "objectClass: ds-cfg-unique-attribute-plugin",
+ "cn: phone Unique Attribute",
+ "ds-cfg-plugin-class: org.opends.server.plugins.UniqueAttributePlugin",
+ "ds-cfg-plugin-enabled: true",
+ "ds-cfg-plugin-type: preOperationAdd",
+ "ds-cfg-plugin-type: preOperationModify",
+ "ds-cfg-plugin-type: preOperationModifyDN",
+ "ds-cfg-unique-attribute-type: telephone",
+ "ds-cfg-unique-attribute-type: mobile",
+ "ds-cfg-unique-attribute-type: fax",
+ "ds-cfg-unique-attribute-base-dn: dc=example,dc=com",
+ "ds-cfg-unique-attribute-base-dn: badDN");
+
+ Object[][] array = new Object[entries.size()][1];
+ for (int i=0; i < array.length; i++)
+ {
+ array[i] = new Object[] { entries.get(i) };
+ }
+
+ return array;
+ }
+
+
+ /**
+ * Tests the process of initializing the server with invalid configurations.
+ *
+ * @param e The configuration entry to use for the initialization.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test(dataProvider = "invalidConfigs",
+ expectedExceptions = { ConfigException.class })
+ public void testInitializeWithInvalidConfigs(Entry e)
+ throws Exception
+ {
+ HashSet<PluginType> pluginTypes = new HashSet<PluginType>();
+ List<Attribute> attrList = e.getAttribute("ds-cfg-plugin-type");
+ for (Attribute a : attrList)
+ {
+ for (AttributeValue v : a.getValues())
+ pluginTypes.add(PluginType.forName(v.getStringValue().toLowerCase()));
+ }
+ UniqueAttributePluginCfg configuration =
+ AdminTestCaseUtils.getConfiguration(
+ UniqueAttributePluginCfgDefn.getInstance(), e);
+ UniqueAttributePlugin plugin = new UniqueAttributePlugin();
+ plugin.initializePlugin(pluginTypes, configuration);
+ plugin.finalizePlugin();
+ }
+
+ /**
+ * Test modify DN operation with various scenerios. See method comments.
+ *
+ * @throws Exception If an unexpected result occurs.
+ */
+ @Test()
+ public void testModDNOperation() throws Exception {
+ //Add an entry under the new superior DN that has a value for uid
+ //that will be tested for.
+ Entry e = makeEntry("cn=test user, ou=new people,o=test");
+ addAttribute(e, "uid", "3user.3");
+ addEntry(e, ResultCode.SUCCESS);
+ //Setup uid attribute to be unique. Test using public naming contexts
+ //for base DNs.
+ addAttrToEntry(uidConfigDN,dsConfigAttrType,"uid");
+ //Rename with new rdn, should fail, there is an entry already with that
+ //uid value.
+ doModDN(DN.decode("uid=3user.3, ou=people, o=test"), RDN.decode("uid=4"),
+ false, null, ResultCode.CONSTRAINT_VIOLATION);
+ //Rename with multi-valued RDN, should fail there is an entry already with
+ //that uid value.
+ doModDN(DN.decode("uid=3user.3, ou=people, o=test"),
+ RDN.decode("sn=xx+uid=4"),
+ false, null, ResultCode.CONSTRAINT_VIOLATION);
+ //Now add a base dn to be unique under, so new superior move can be tested.
+ addAttrToEntry(uidConfigDN,dsConfigBaseDN,"ou=new people,o=test");
+
+
+ //Try to move the entry to a new superior.
+ //Should fail, there is an entry under the new superior already with
+ //that uid value.
+ doModDN(DN.decode("uid=3user.3, ou=people, o=test"),
+ RDN.decode("uid=3user.3"), false,
+ DN.decode("ou=new people, o=test"),
+ ResultCode.CONSTRAINT_VIOLATION);
+ //Test again with different superior, should succeed, new superior DN is
+ //not in base DN scope.
+ doModDN(DN.decode("uid=3user.3, ou=people, o=test"),
+ RDN.decode("uid=3user.3"), false,
+ DN.decode("ou=new people1, o=test"),
+ ResultCode.SUCCESS);
+ }
+
+ /**
+ * Test various modifcation scenerios using a configuration with no base
+ * DNs defined. Use default of public naming contexts for base DNs.
+ *
+ * @throws Exception If an unexpected result occurs.
+ */
+ @Test()
+ public void testModOperationNameContexts() throws Exception {
+ addAttrToEntry(uidConfigDN,dsConfigAttrType,"mail");
+ LinkedList<Modification> mods = new LinkedList<Modification>();
+ addMods(mods,"mail",ModificationType.REPLACE,"userx@test","userxx@test",
+ "user1t@test");
+ //Fail because user1t@test already exists under "o=people,o=test".
+ doMods(mods, DN.decode("uid=5user.5,ou=People,o=test"),
+ ResultCode.CONSTRAINT_VIOLATION);
+ mods.clear();
+ addMods(mods,"pager",ModificationType.ADD,"2-999-1234","1-999-5678");
+ addMods(mods,"mail",ModificationType.ADD,"userx@test","userxx@test",
+ "user1t@test");
+ //Fail because user1t@test already exists under "o=people,o=test".
+ doMods(mods, DN.decode("uid=5user.5,ou=People,o=test"),
+ ResultCode.CONSTRAINT_VIOLATION);
+ mods.clear();
+ addMods(mods,"pager",ModificationType.ADD,"2-999-1234","1-999-5678");
+ addMods(mods,"mail",ModificationType.REPLACE,"userx@test","userxx@test",
+ "user1t@test");
+ //Ok because adding mail value user1t@test to entry that already
+ //contains mail value user1t@test.
+ doMods(mods, DN.decode("uid=1user.1,ou=People,o=test"),
+ ResultCode.SUCCESS);
+ mods.clear();
+ //Remove mail as the unique attribute.
+ deleteAttrsFromEntry(uidConfigDN,dsConfigAttrType);
+ //Add employeenumber as the unique attribute.
+ addAttrToEntry(uidConfigDN,dsConfigAttrType,"employeenumber");
+ addMods(mods,"employeenumber",ModificationType.INCREMENT,"1");
+ //Test modify increment extension.
+ //Fail because incremented value of employeenumber (2) already exists.
+ doMods(mods, DN.decode("uid=1user.1,ou=People,o=test"),
+ ResultCode.CONSTRAINT_VIOLATION);
+ }
+
+
+ /**
+ * Test setting the plugins up to get DSEE behavior. Basically two or more
+ * base DNs can have the same value, but not within the trees. This uses two
+ * plugins to accomplish this.
+ *
+ * @throws Exception If an unexpected result occurs.
+ */
+ @Test()
+ public void testDseeCompatAdd() throws Exception {
+ //Set up one plugin with mail attribute and a suffix.
+ addAttrToEntry(uidConfigDN,dsConfigAttrType,"mail");
+ addAttrToEntry(uidConfigDN,dsConfigBaseDN,"ou=People,o=test");
+ //Set up another plugin with the mail attribute and a different suffix.
+ addAttrToEntry(testConfigDN,dsConfigAttrType,"mail");
+ addAttrToEntry(testConfigDN,dsConfigBaseDN,"ou=People1,o=test");
+ //Add two entries with same mail attribute value into the different
+ //base DNs.
+ Entry e1 = makeEntry("cn=test user1, ou=People,o=test");
+ addAttribute(e1, "mail", "mailtest@test");
+ addEntry(e1, ResultCode.SUCCESS);
+ Entry e2 = makeEntry("cn=test user2, ou=People1,o=test");
+ addAttribute(e2, "mail", "mailtest@test");
+ addEntry(e2, ResultCode.SUCCESS);
+ //Now try to add two more entries with the same mail attribute value.
+ Entry e3 = makeEntry("cn=test user3, ou=People,o=test");
+ addAttribute(e3, "mail", "mailtest@test");
+ addEntry(e3, ResultCode.CONSTRAINT_VIOLATION);
+ Entry e4 = makeEntry("cn=test user4, ou=People1,o=test");
+ addAttribute(e4, "mail", "mailtest@test");
+ addEntry(e4, ResultCode.CONSTRAINT_VIOLATION);
+ }
+
+ /**
+ * Test various add operation scenerios using defined base DNs.
+ * See comments in method.
+ *
+ * @throws Exception If an unexpected result occurs.
+ */
+ @Test()
+ public void testAddOperation() throws Exception {
+ addAttrToEntry(uidConfigDN,dsConfigAttrType,"mail");
+ addAttrToEntry(uidConfigDN,dsConfigBaseDN,"ou=People1,o=test");
+ addAttrToEntry(uidConfigDN,dsConfigBaseDN,"ou=People,o=test");
+ Entry e = makeEntry("cn=test user, ou=People,o=test");
+ addAttribute(e, "mail", "user1t@test");
+ //Fail because mail attribute already exists under "ou=people,o=test".
+ addEntry(e, ResultCode.CONSTRAINT_VIOLATION);
+ delAttribute(e, "mail");
+ //Remove mail attribute type from config.
+ deleteAttrsFromEntry(uidConfigDN,dsConfigAttrType);
+ //Add mobile, pager, telephonenumber to config.
+ addAttrToEntry(uidConfigDN,dsConfigAttrType,"mobile",
+ "pager","telephonenumber");
+ addAttribute(e, "mobile", "1-999-1234","1-999-5678","1-444-9012");
+ addEntry(e, ResultCode.CONSTRAINT_VIOLATION);
+ e.setDN(DN.decode("cn=test user, ou=People,o=test"));
+ //Fail because "2-333-9012" already exists in "ou=people,o=test" in
+ //telephonenumber attribute.
+ addEntry(e, ResultCode.CONSTRAINT_VIOLATION);
+ delAttribute(e, "mobile");
+ addAttribute(e, "pager", "2-111-1234","1-999-5678","1-999-9012");
+ //Fail because "2-111-9012" already exists in "ou=people1,o=test" in
+ //mobile attribute.
+ addEntry(e, ResultCode.CONSTRAINT_VIOLATION);
+ //Test two plugin configuration. Add mail attribute to second plugin
+ //instance, leave the first instance as it is.
+ addAttrToEntry(testConfigDN,dsConfigAttrType,"mail");
+ //Add suffix to second plugin.
+ addAttrToEntry(testConfigDN,dsConfigBaseDN,"ou=People,o=test");
+ delAttribute(e, "pager");
+ //Add some values that will pass the first plugin.
+ addAttribute(e, "telephonenumber", "2-999-1234","1-999-5678","1-999-9012");
+ //Add a value that will fail the second plugin.
+ addAttribute(e, "mail", "user1t@test");
+ //Should pass frirail through second plugin configuration.
+ addEntry(e, ResultCode.CONSTRAINT_VIOLATION);
+ }
+
+
+ /**
+ * Test attempting to add entries using a configuration with no base
+ * DNs defined. Use default of public naming contexts for base DNs.
+ *
+ * @throws Exception If an unexpected result occurs.
+ */
+ @Test()
+ public void testAddOperationNameContext() throws Exception {
+ addAttrToEntry(uidConfigDN,dsConfigAttrType,"mail");
+ Entry e = makeEntry("cn=test user, ou=People,o=test");
+ addAttribute(e, "mail", "user77x@test");
+ //Fail because mail value "user77x@test" is a value under the
+ //"dc=example,dc=com" naming context.
+ addEntry(e, ResultCode.CONSTRAINT_VIOLATION);
+ delAttribute(e, "mail");
+ deleteAttrsFromEntry(uidConfigDN,dsConfigAttrType);
+ addAttrToEntry(uidConfigDN,dsConfigAttrType,"mobile",
+ "pager","telephonenumber");
+ addAttribute(e, "mobile", "1-999-1234","1-999-5678","2-777-9012");
+ //Fail because "2-777-9012" is a telephone value under the
+ //"dc=example,dc=com" naming context.
+ addEntry(e, ResultCode.CONSTRAINT_VIOLATION);
+ e.setDN(DN.decode("cn=test user, ou=People,o=test"));
+ addEntry(e, ResultCode.CONSTRAINT_VIOLATION);
+ delAttribute(e, "mobile");
+ addAttribute(e, "pager", "2-777-1234","1-999-5678","1-999-9012");
+ //Fail because "2-777-9012" is a telephone value under the
+ //"dc=example,dc=com" naming context.
+ addEntry(e, ResultCode.CONSTRAINT_VIOLATION);
+ }
+
+
+ /**
+ * Create entries under the specified suffix and add them to the server.
+ * The character argument is used to make the mail attribute unique.
+ *
+ * @param suffix The suffix to use in building the entries.
+ * @param c Character used to make the mail attribute unique.
+ * @throws Exception If a problem occurs.
+ */
+ private void addTestEntries(String suffix, char c) throws Exception {
+ TestCaseUtils.addEntries(
+ "dn: ou=People," + suffix,
+ "objectClass: top",
+ "objectClass: organizationalUnit",
+ "ou: People",
+ "aci: (targetattr= \"*\")" +
+ "(version 3.0; acl \"allow all\";" +
+ "allow(all) userdn=\"ldap:///anyone\";)",
+ "",
+ "dn: ou=People1," + suffix,
+ "objectClass: top",
+ "objectClass: organizationalUnit",
+ "ou: People1",
+ "aci: (targetattr= \"*\")" +
+ "(version 3.0; acl \"allow all\";" +
+ "allow(all) userdn=\"ldap:///anyone\";)",
+ "",
+ "dn: ou=New People1," + suffix,
+ "objectClass: top",
+ "objectClass: organizationalUnit",
+ "ou: New People",
+ "",
+ "",
+ "dn: ou=New People," + suffix,
+ "objectClass: top",
+ "objectClass: organizationalUnit",
+ "ou: New People",
+ "",
+ "dn: uid=1user.1,ou=People," + suffix,
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: 1",
+ "givenName: 1User",
+ "sn: 1",
+ "cn: 1User 1",
+ "userPassword: password",
+ "mail: user1" + c +"@test",
+ "employeeNumber: 1",
+ "mobile: 1-111-1234",
+ "pager: 1-111-5678",
+ "telephoneNumber: 1-111-9012",
+ "",
+ "dn: uid=2user.2,ou=People," + suffix,
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: 2",
+ "givenName: 2User",
+ "sn: 2",
+ "cn: User 2",
+ "mail: user2" + c + "@test",
+ "userPassword: password",
+ "employeeNumber: 2",
+ "mobile: 1-222-1234",
+ "pager: 1-222-5678",
+ "telephoneNumber: 1-222-9012",
+ "",
+ "dn: uid=3user.3,ou=People," + suffix,
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: 3",
+ "givenName: 3User",
+ "sn: 3",
+ "cn: User 3",
+ "mail: user3" + c + "@test",
+ "userPassword: password",
+ "employeeNumber: 3",
+ "mobile: 1-333-1234",
+ "pager: 1-333-5678",
+ "telephoneNumber: 1-333-9012",
+ "",
+ "dn: uid=4user.4,ou=People," + suffix,
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: 4",
+ "givenName: 4User",
+ "sn: 4",
+ "cn: User 4",
+ "mail: user4" + c + "@test",
+ "userPassword: password",
+ "employeeNumber: 4",
+ "mobile: 1-444-1234",
+ "pager: 1-444-5678",
+ "telephoneNumber: 1-444-9012",
+ "",
+ "dn: uid=5user.5,ou=People," + suffix,
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: 5",
+ "givenName: 5User",
+ "sn: 5",
+ "cn: User 5",
+ "mail: user5" + c + "@test",
+ "userPassword: password",
+ "employeeNumber: 5",
+ "mobile: 1-555-1234",
+ "pager: 1-555-5678",
+ "telephoneNumber: 1-555-9012",
+ "",
+ "dn: uid=1user.1,ou=People1," + suffix,
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: 1",
+ "givenName: 1User",
+ "sn: 11",
+ "cn: 1User 11",
+ "userPassword: password",
+ "mail: user11" + c + "@test",
+ "employeeNumber: 111",
+ "mobile: 2-111-1234",
+ "pager: 2-111-5678",
+ "telephoneNumber: 2-111-9012",
+ "",
+ "dn: uid=2user.22,ou=People1," + suffix,
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: 2",
+ "givenName: 2User",
+ "sn: 22",
+ "cn: User 22",
+ "mail: user22" + c + "@test",
+ "userPassword: password",
+ "employeeNumber: 222",
+ "mobile: 2-222-1234",
+ "pager: 2-222-5678",
+ "telephoneNumber: 2-222-9012",
+ "",
+ "dn: uid=3user.33,ou=People1," + suffix,
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: 33",
+ "givenName: 3User",
+ "sn: 3",
+ "cn: User 33",
+ "mail: user33" + c + "@test",
+ "userPassword: password",
+ "employeeNumber: 333",
+ "mobile: 2-333-1234",
+ "pager: 2-333-5678",
+ "telephoneNumber: 2-333-9012"
+ );
+ //Add an additional entry if the suffix is "dc=example,dc=com".
+ if(suffix.equals("dc=example,dc=com")) {
+ TestCaseUtils.addEntries(
+ "dn: uid=2user.77,ou=People," + suffix,
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: 2",
+ "givenName: 2User",
+ "sn: 22",
+ "cn: User 22",
+ "mail: user77" + c + "@test",
+ "userPassword: password",
+ "employeeNumber: 777",
+ "mobile: 2-777-1234",
+ "pager: 2-777-5678",
+ "telephoneNumber: 2-777-9012"
+ );
+ }
+ }
+
+ /**
+ * Remove the attributes specified by the attribute type strings from the
+ * entry corresponding to the dn argument.
+ *
+ * @param dn The entry to remove the attributes from.
+ * @param attrTypeStrings The attribute type string list to remove from the
+ * entry.
+ * @throws Exception If an error occurs.
+ */
+ private void
+ deleteAttrsFromEntry(DN dn, String... attrTypeStrings) throws Exception {
+ LinkedList<Modification> mods = new LinkedList<Modification>();
+ for(String attrTypeString : attrTypeStrings) {
+ AttributeType attrType = getAttrType(attrTypeString);
+ mods.add(new Modification(ModificationType.DELETE,
+ new Attribute(attrType)));
+ }
+ InternalClientConnection conn =
+ InternalClientConnection.getRootConnection();
+ conn.processModify(dn, mods);
+ }
+
+ /**
+ * Attempt to add an attribute of attribute type string to the entry
+ * specified by the dn argument. The values to use in the attribute creation
+ * is specified by the variable argument list.
+ *
+ * @param dn The dn of the entry to add the attribute.
+ * @param attrTypeString The attribute type string.
+ * @param attrValStrings The values of the attribute.
+ */
+ private void
+ addAttrToEntry(DN dn, String attrTypeString, String... attrValStrings) {
+ LinkedList<Modification> mods = new LinkedList<Modification>();
+ LinkedHashSet<AttributeValue> attrValues =
+ new LinkedHashSet<AttributeValue>();
+ AttributeType attrType = getAttrType(attrTypeString);
+ for(String valString : attrValStrings)
+ attrValues.add(new AttributeValue(attrType, valString));
+ Attribute attr = new Attribute(attrType, attrTypeString, attrValues);
+ mods.add(new Modification(ModificationType.ADD, attr));
+ InternalClientConnection conn =
+ InternalClientConnection.getRootConnection();
+ conn.processModify(dn, mods);
+ }
+
+ /**
+ * Try to add an entry to the server checking for the expected return
+ * code.
+ *
+ * @param e The entry to add.
+ * @param rc The expected return code.
+ * @throws Exception If an error occurs.
+ */
+ private void addEntry(Entry e, ResultCode rc) throws Exception {
+ InternalClientConnection conn =
+ InternalClientConnection.getRootConnection();
+ AddOperation addOperation = conn.processAdd(e);
+ assertEquals(addOperation.getResultCode(), rc);
+ }
+
+ /**
+ * Make a entry with the specified dn.
+ *
+ * @param dn The dn of the entry.
+ * @return The created entry.
+ * @throws Exception If the entry can't be created.
+ */
+ private Entry makeEntry(String dn) throws Exception {
+ return TestCaseUtils.makeEntry(
+ "dn: " + dn,
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: 1",
+ "givenName: 1User",
+ "sn: 1",
+ "cn: 1User 1"
+ );
+ }
+
+ /**
+ * Remove an attribute from the specified entry.
+ *
+ * @param entry The entry to remove the attribute from.
+ * @param attrTypeString The attribute type string to remove.
+ */
+ private void delAttribute(Entry entry, String attrTypeString) {
+ entry.removeAttribute(getAttrType(attrTypeString));
+ }
+
+ /**
+ * Add an attribute to an entry with specified values.
+ *
+ * @param entry The entry to add the attribute to.
+ * @param attrTypeString The attribute type string name.
+ * @param attrValues The values use in building the attribute.
+ */
+ private void
+ addAttribute(Entry entry, String attrTypeString, String... attrValues) {
+ LinkedHashSet<AttributeValue> values=new LinkedHashSet<AttributeValue>();
+ AttributeType attrType=getAttrType(attrTypeString);
+ for(String attrValue : attrValues) {
+ AttributeValue value = new AttributeValue(attrType, attrValue);
+ values.add(value);
+ }
+ entry.addAttribute(new Attribute(attrType, attrTypeString, values), null);
+ }
+
+ /**
+ * Add a new modification for attribute type string and values of modification
+ * type to a list of modifications.
+ *
+ * @param mods The modification list to add to.
+ * @param attrTypeString The attribute type string name.
+ * @param modificationType The modification type.
+ * @param attrValues The values to build the modification from.
+ */
+ private void
+ addMods(LinkedList<Modification> mods, String attrTypeString,
+ ModificationType modificationType, String... attrValues) {
+ LinkedHashSet<AttributeValue> values=new LinkedHashSet<AttributeValue>();
+ AttributeType attrType=getAttrType(attrTypeString);
+ for(String attrValue : attrValues) {
+ AttributeValue value = new AttributeValue(attrType, attrValue);
+ values.add(value);
+ }
+ mods.add(new Modification(modificationType,
+ new Attribute(attrType, attrTypeString, values)));
+ }
+
+ /**
+ * Return the attribute type corresponding to the attribute type string.
+ *
+ * @param attrTypeString The attribute type string name.
+ *
+ * @return An attribute type object pertaining to the string.
+ */
+ private AttributeType getAttrType(String attrTypeString) {
+ AttributeType attrType =
+ DirectoryServer.getAttributeType(attrTypeString);
+ if (attrType == null)
+ attrType = DirectoryServer.getDefaultAttributeType(attrTypeString);
+ return attrType;
+ }
+
+ /**
+ * Perform modify operation with list of modifications. Expect return code
+ * of value rc.
+ *
+ * @param mods The modification list to use.
+ * @param dn The DN of the entry to modify.
+ * @param rc The expected return code.
+ */
+ private void
+ doMods(LinkedList<Modification> mods, DN dn, ResultCode rc ) {
+ InternalClientConnection conn =
+ InternalClientConnection.getRootConnection();
+ ModifyOperation modifyOperation =
+ conn.processModify(dn, mods);
+ assertEquals(modifyOperation.getResultCode(), rc);
+ }
+
+ /**
+ * Perform modify DN operation. Expect return value of rc.
+ *
+ * @param dn The DN to renmame or move.
+ * @param rdn RDN value.
+ * @param delOld Delete old flag.
+ * @param newSuperior New superior to move to.
+ * @param rc Expected return code from operation.
+ */
+ private void
+ doModDN(DN dn, RDN rdn, boolean delOld, DN newSuperior, ResultCode rc) {
+ InternalClientConnection conn =
+ InternalClientConnection.getRootConnection();
+ ModifyDNOperation modifyDNOperation =
+ conn.processModifyDN(dn, rdn, delOld, newSuperior);
+ assertEquals(modifyDNOperation.getResultCode(), rc);
+ }
+}
--
Gitblit v1.10.0