| opends/resource/schema/02-config.ldif | ●●●●● patch | view | raw | blame | history | |
| opends/src/admin/defn/org/opends/server/admin/std/AttributeCleanupPluginConfiguration.xml | ●●●●● patch | view | raw | blame | history | |
| opends/src/admin/messages/AttributeCleanupPluginCfgDefn.properties | ●●●●● patch | view | raw | blame | history | |
| opends/src/messages/messages/plugin.properties | ●●●●● patch | view | raw | blame | history | |
| opends/src/server/org/opends/server/plugins/AttributeCleanupPlugin.java | ●●●●● patch | view | raw | blame | history | |
| opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/AttributeCleanupPluginTestCase.java | ●●●●● patch | view | raw | blame | history |
opends/resource/schema/02-config.ldif
@@ -2660,6 +2660,14 @@ NO-USER-MODIFICATION USAGE directoryOperation X-ORIGIN 'OpenDJ Directory Server' ) attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.28 NAME 'ds-cfg-remove-inbound-attributes' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'OpenDJ Directory Server' ) attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.29 NAME 'ds-cfg-rename-inbound-attributes' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'OpenDJ Directory Server' ) objectClasses: ( 1.3.6.1.4.1.26027.1.2.1 NAME 'ds-cfg-access-control-handler' SUP top @@ -4426,3 +4434,10 @@ ds-cfg-cached-password-storage-scheme $ ds-cfg-cached-password-ttl ) X-ORIGIN 'OpenDJ Directory Server' ) objectClasses: ( 1.3.6.1.4.1.36733.2.1.2.5 NAME 'ds-cfg-attribute-cleanup-plugin' SUP ds-cfg-plugin STRUCTURAL MAY ( ds-cfg-remove-inbound-attributes $ ds-cfg-rename-inbound-attributes ) X-ORIGIN 'OpenDJ Directory Server' ) opends/src/admin/defn/org/opends/server/admin/std/AttributeCleanupPluginConfiguration.xml
New file @@ -0,0 +1,126 @@ <?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 ! ! ! Copyright 2011 profiq s.r.o. ! Portions copyright 2011 ForgeRock AS. ! --> <adm:managed-object name="attribute-cleanup-plugin" plural-name="attribute-cleanup-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> A pre-parse plugin which can be used to remove and rename attributes in ADD and MODIFY requests before being processed. </adm:synopsis> <adm:description> This plugin should be used in order maintain interoperability with client applications which attempt to update attributes in a way which is incompatible with LDAPv3 or OpenDJ. For example, this plugin may be used in order to remove changes to operational attributes such as modifiersName, creatorsName, modifyTimestamp, and createTimestamp (Sun DSEE chaining does this). </adm:description> <adm:profile name="ldap"> <ldap:object-class> <ldap:name>ds-cfg-attribute-cleanup-plugin</ldap:name> <ldap:superior>ds-cfg-plugin</ldap:superior> </ldap:object-class> </adm:profile> <adm:property-override name="java-class"> <adm:default-behavior> <adm:defined> <adm:value>org.opends.server.plugins.AttributeCleanupPlugin</adm:value> </adm:defined> </adm:default-behavior> </adm:property-override> <adm:property-override name="invoke-for-internal-operations"> <adm:default-behavior> <adm:defined> <adm:value>false</adm:value> </adm:defined> </adm:default-behavior> </adm:property-override> <adm:property-override name="plugin-type" advanced="true"> <adm:default-behavior> <adm:defined> <adm:value>preparseadd</adm:value> <adm:value>preparsemodify</adm:value> </adm:defined> </adm:default-behavior> </adm:property-override> <adm:property name="remove-inbound-attributes" multi-valued="true"> <adm:synopsis> A list of attributes which should be removed from incoming add or modify requests. </adm:synopsis> <adm:default-behavior> <adm:alias> <adm:synopsis>No attributes will be removed</adm:synopsis> </adm:alias> </adm:default-behavior> <adm:syntax> <!-- Use string syntax because we may be removing invalid attributes --> <adm:string /> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-remove-inbound-attributes</ldap:name> </ldap:attribute> </adm:profile> </adm:property> <adm:property name="rename-inbound-attributes" multi-valued="true"> <adm:synopsis> A list of attributes which should be renamed in incoming add or modify requests. </adm:synopsis> <adm:default-behavior> <adm:alias> <adm:synopsis>No attributes will be renamed</adm:synopsis> </adm:alias> </adm:default-behavior> <adm:syntax> <adm:string> <adm:pattern> <adm:regex>^[^:]+:[^:]+$</adm:regex> <adm:usage>FROM:TO</adm:usage> <adm:synopsis>An attribute name mapping.</adm:synopsis> </adm:pattern> </adm:string> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-rename-inbound-attributes</ldap:name> </ldap:attribute> </adm:profile> </adm:property> </adm:managed-object> opends/src/admin/messages/AttributeCleanupPluginCfgDefn.properties
New file @@ -0,0 +1,67 @@ user-friendly-name=Attribute Cleanup Plugin user-friendly-plural-name=Attribute Cleanup Plugins synopsis=A pre-parse plugin which can be used to remove and rename attributes in ADD and MODIFY requests before being processed. description=This plugin should be used in order maintain interoperability with client applications which attempt to update attributes in a way which is incompatible with LDAPv3 or OpenDJ. For example, this plugin may be used in order to remove changes to operational attributes such as modifiersName, creatorsName, modifyTimestamp, and createTimestamp (Sun DSEE chaining does this). property.enabled.synopsis=Indicates whether the plug-in is enabled for use. property.invoke-for-internal-operations.synopsis=Indicates whether the plug-in should be invoked for internal operations. property.invoke-for-internal-operations.description=Any plug-in that can be invoked for internal operations must ensure that it does not create any new internal operatons that can cause the same plug-in to be re-invoked. property.java-class.synopsis=Specifies the fully-qualified name of the Java class that provides the plug-in implementation. property.plugin-type.synopsis=Specifies the set of plug-in types for the plug-in, which specifies the times at which the plug-in is invoked. property.plugin-type.syntax.enumeration.value.intermediateresponse.synopsis=Invoked before sending an intermediate repsonse message to the client. property.plugin-type.syntax.enumeration.value.ldifexport.synopsis=Invoked for each operation to be written during an LDIF export. property.plugin-type.syntax.enumeration.value.ldifimport.synopsis=Invoked for each entry read during an LDIF import. property.plugin-type.syntax.enumeration.value.ldifimportbegin.synopsis=Invoked at the beginning of an LDIF import session. property.plugin-type.syntax.enumeration.value.ldifimportend.synopsis=Invoked at the end of an LDIF import session. property.plugin-type.syntax.enumeration.value.postconnect.synopsis=Invoked whenever a new connection is established to the server. property.plugin-type.syntax.enumeration.value.postdisconnect.synopsis=Invoked whenever an existing connection is terminated (by either the client or the server). property.plugin-type.syntax.enumeration.value.postoperationabandon.synopsis=Invoked after completing the abandon processing. property.plugin-type.syntax.enumeration.value.postoperationadd.synopsis=Invoked after completing the core add processing but before sending the response to the client. property.plugin-type.syntax.enumeration.value.postoperationbind.synopsis=Invoked after completing the core bind processing but before sending the response to the client. property.plugin-type.syntax.enumeration.value.postoperationcompare.synopsis=Invoked after completing the core compare processing but before sending the response to the client. property.plugin-type.syntax.enumeration.value.postoperationdelete.synopsis=Invoked after completing the core delete processing but before sending the response to the client. property.plugin-type.syntax.enumeration.value.postoperationextended.synopsis=Invoked after completing the core extended processing but before sending the response to the client. property.plugin-type.syntax.enumeration.value.postoperationmodify.synopsis=Invoked after completing the core modify processing but before sending the response to the client. property.plugin-type.syntax.enumeration.value.postoperationmodifydn.synopsis=Invoked after completing the core modify DN processing but before sending the response to the client. property.plugin-type.syntax.enumeration.value.postoperationsearch.synopsis=Invoked after completing the core search processing but before sending the response to the client. property.plugin-type.syntax.enumeration.value.postoperationunbind.synopsis=Invoked after completing the unbind processing. property.plugin-type.syntax.enumeration.value.postresponseadd.synopsis=Invoked after sending the add response to the client. property.plugin-type.syntax.enumeration.value.postresponsebind.synopsis=Invoked after sending the bind response to the client. property.plugin-type.syntax.enumeration.value.postresponsecompare.synopsis=Invoked after sending the compare response to the client. property.plugin-type.syntax.enumeration.value.postresponsedelete.synopsis=Invoked after sending the delete response to the client. property.plugin-type.syntax.enumeration.value.postresponseextended.synopsis=Invoked after sending the extended response to the client. property.plugin-type.syntax.enumeration.value.postresponsemodify.synopsis=Invoked after sending the modify response to the client. property.plugin-type.syntax.enumeration.value.postresponsemodifydn.synopsis=Invoked after sending the modify DN response to the client. property.plugin-type.syntax.enumeration.value.postresponsesearch.synopsis=Invoked after sending the search result done message to the client. property.plugin-type.syntax.enumeration.value.postsynchronizationadd.synopsis=Invoked after completing post-synchronization processing for an add operation. property.plugin-type.syntax.enumeration.value.postsynchronizationdelete.synopsis=Invoked after completing post-synchronization processing for a delete operation. property.plugin-type.syntax.enumeration.value.postsynchronizationmodify.synopsis=Invoked after completing post-synchronization processing for a modify operation. property.plugin-type.syntax.enumeration.value.postsynchronizationmodifydn.synopsis=Invoked after completing post-synchronization processing for a modify DN operation. property.plugin-type.syntax.enumeration.value.preoperationadd.synopsis=Invoked prior to performing the core add processing. property.plugin-type.syntax.enumeration.value.preoperationbind.synopsis=Invoked prior to performing the core bind processing. property.plugin-type.syntax.enumeration.value.preoperationcompare.synopsis=Invoked prior to performing the core compare processing. property.plugin-type.syntax.enumeration.value.preoperationdelete.synopsis=Invoked prior to performing the core delete processing. property.plugin-type.syntax.enumeration.value.preoperationextended.synopsis=Invoked prior to performing the core extended processing. property.plugin-type.syntax.enumeration.value.preoperationmodify.synopsis=Invoked prior to performing the core modify processing. property.plugin-type.syntax.enumeration.value.preoperationmodifydn.synopsis=Invoked prior to performing the core modify DN processing. property.plugin-type.syntax.enumeration.value.preoperationsearch.synopsis=Invoked prior to performing the core search processing. property.plugin-type.syntax.enumeration.value.preparseabandon.synopsis=Invoked prior to parsing an abandon request. property.plugin-type.syntax.enumeration.value.preparseadd.synopsis=Invoked prior to parsing an add request. property.plugin-type.syntax.enumeration.value.preparsebind.synopsis=Invoked prior to parsing a bind request. property.plugin-type.syntax.enumeration.value.preparsecompare.synopsis=Invoked prior to parsing a compare request. property.plugin-type.syntax.enumeration.value.preparsedelete.synopsis=Invoked prior to parsing a delete request. property.plugin-type.syntax.enumeration.value.preparseextended.synopsis=Invoked prior to parsing an extended request. property.plugin-type.syntax.enumeration.value.preparsemodify.synopsis=Invoked prior to parsing a modify request. property.plugin-type.syntax.enumeration.value.preparsemodifydn.synopsis=Invoked prior to parsing a modify DN request. property.plugin-type.syntax.enumeration.value.preparsesearch.synopsis=Invoked prior to parsing a search request. property.plugin-type.syntax.enumeration.value.preparseunbind.synopsis=Invoked prior to parsing an unbind request. property.plugin-type.syntax.enumeration.value.searchresultentry.synopsis=Invoked before sending a search result entry to the client. property.plugin-type.syntax.enumeration.value.searchresultreference.synopsis=Invoked before sending a search result reference to the client. property.plugin-type.syntax.enumeration.value.shutdown.synopsis=Invoked during a graceful directory server shutdown. property.plugin-type.syntax.enumeration.value.startup.synopsis=Invoked during the directory server startup process. property.plugin-type.syntax.enumeration.value.subordinatedelete.synopsis=Invoked in the course of deleting a subordinate entry of a delete operation. property.plugin-type.syntax.enumeration.value.subordinatemodifydn.synopsis=Invoked in the course of moving or renaming an entry subordinate to the target of a modify DN operation. property.remove-inbound-attributes.synopsis=A list of attributes which should be removed from incoming add or modify requests. property.remove-inbound-attributes.default-behavior.alias.synopsis=No attributes will be removed property.rename-inbound-attributes.synopsis=A list of attributes which should be renamed in incoming add or modify requests. property.rename-inbound-attributes.default-behavior.alias.synopsis=No attributes will be renamed property.rename-inbound-attributes.syntax.string.pattern.synopsis=An attribute name mapping. opends/src/messages/messages/plugin.properties
@@ -428,3 +428,11 @@ synchronization plugin could not encode a password for the following reasons: %s SEVERE_ERR_PLUGIN_SAMBA_SYNC_MODIFICATION_PROCESSING_119=The Samba password \ synchronization plugin could not process a modification for the following reason: %s SEVERE_ERR_PLUGIN_ATTR_CLEANUP_INITIALIZE_PLUGIN_120=Invalid plugin type '%s' \ for the Attribute Cleanup plugin SEVERE_ERR_PLUGIN_ATTR_CLEANUP_ATTRIBUTE_MISSING_121=Attribute '%s' is not \ defined in the directory schema SEVERE_ERR_PLUGIN_ATTR_CLEANUP_DUPLICATE_VALUE_122=The attribute '%s' has \ already been defined in the configuration SEVERE_ERR_PLUGIN_ATTR_CLEANUP_EQUAL_VALUES_123=The mapping '%s:%s' maps the \ attribute to itself opends/src/server/org/opends/server/plugins/AttributeCleanupPlugin.java
New file @@ -0,0 +1,493 @@ /* * 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 * * * Copyright 2011 profiq s.r.o. * Portions copyright 2011 ForgeRock AS. */ package org.opends.server.plugins; import static org.opends.messages.PluginMessages.*; import static org.opends.server.loggers.debug.DebugLogger.debugEnabled; import static org.opends.server.util.StaticUtils.toLowerCase; import java.util.*; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; import org.opends.messages.Message; import org.opends.server.admin.server.ConfigurationChangeListener; import org.opends.server.admin.std.server.AttributeCleanupPluginCfg; import org.opends.server.admin.std.server.PluginCfg; import org.opends.server.api.plugin.DirectoryServerPlugin; import org.opends.server.api.plugin.PluginResult; import org.opends.server.api.plugin.PluginType; import org.opends.server.config.ConfigException; import org.opends.server.core.DirectoryServer; import org.opends.server.loggers.debug.DebugLogger; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.types.*; import org.opends.server.types.operation.PreParseAddOperation; import org.opends.server.types.operation.PreParseModifyOperation; /** * The attribute cleanup plugin implementation class. The plugin removes and/or * renames the configured parameters from the incoming ADD and MODIFY requests. */ public class AttributeCleanupPlugin extends DirectoryServerPlugin<AttributeCleanupPluginCfg> implements ConfigurationChangeListener<AttributeCleanupPluginCfg> { /** * Plugin configuration. */ private AttributeCleanupPluginCfg config; /** * Debug tracer. */ private static final DebugTracer TRACER = DebugLogger.getTracer(); /** * A table of attributes to be renamed. */ private Map<String, String> attributesToRename; /** * The set of attributes to be removed. */ private Set<String> attributesToRemove; /** * This lock prevents concurrent updates to the configuration while operations * are being processed. */ private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); private final ReadLock sharedLock = lock.readLock(); private final WriteLock exclusiveLock = lock.writeLock(); /** * Default constructor. */ public AttributeCleanupPlugin() { super(); } /** * {@inheritDoc} */ @Override public ConfigChangeResult applyConfigurationChange( final AttributeCleanupPluginCfg config) { exclusiveLock.lock(); try { /* Apply the change, as at this point is has been validated. */ this.config = config; attributesToRename = new HashMap<String, String>(); for (final String mapping : config.getRenameInboundAttributes()) { final int colonPos = mapping.lastIndexOf(":"); final String fromAttr = mapping.substring(0, colonPos).trim(); final String toAttr = mapping.substring(colonPos + 1).trim(); attributesToRename.put(toLowerCase(fromAttr), toLowerCase(toAttr)); } attributesToRemove = new HashSet<String>(); for (final String attr : config.getRemoveInboundAttributes()) { attributesToRemove.add(toLowerCase(attr.trim())); } /* Update was successful, no restart required. */ return new ConfigChangeResult(ResultCode.SUCCESS, false); } finally { exclusiveLock.unlock(); } } /** * {@inheritDoc} */ @Override public PluginResult.PreParse doPreParse( final PreParseAddOperation addOperation) { sharedLock.lock(); try { /* * First strip the listed attributes, then rename the ones that remain. */ processInboundRemove(addOperation); processInboundRename(addOperation); return PluginResult.PreParse.continueOperationProcessing(); } finally { sharedLock.unlock(); } } /** * {@inheritDoc} */ @Override public PluginResult.PreParse doPreParse( final PreParseModifyOperation modifyOperation) { sharedLock.lock(); try { /* * First strip the listed attributes, then rename the ones that remain. */ processInboundRemove(modifyOperation); processInboundRename(modifyOperation); /* * If the MODIFY request has been stripped of ALL modifications, stop the * processing and return SUCCESS to the client. */ if (modifyOperation.getRawModifications().isEmpty()) { if (debugEnabled()) { TRACER.debugVerbose("The AttributeCleanupPlugin has eliminated all " + "modifications. The processing should be stopped."); } return PluginResult.PreParse.stopProcessing(ResultCode.SUCCESS, null); } return PluginResult.PreParse.continueOperationProcessing(); } finally { sharedLock.unlock(); } } /** * {@inheritDoc} */ @Override public void finalizePlugin() { /* * It's not essential to take the lock here, but we will anyhow for * consistency with other methods. */ exclusiveLock.lock(); try { /* Deregister change listeners. */ config.removeAttributeCleanupChangeListener(this); } finally { exclusiveLock.unlock(); } } /** * {@inheritDoc} */ @Override() public void initializePlugin(final Set<PluginType> pluginTypes, final AttributeCleanupPluginCfg configuration) throws ConfigException, InitializationException { /* * The plugin should be invoked only for pre-parse ADD and MODIFY * operations. */ for (final PluginType t : pluginTypes) { switch (t) { case PRE_PARSE_ADD: break; case PRE_PARSE_MODIFY: break; default: final Message message = ERR_PLUGIN_ATTR_CLEANUP_INITIALIZE_PLUGIN .get(String.valueOf(t)); throw new ConfigException(message); } } /* Verify the current configuration. */ final List<Message> messages = new LinkedList<Message>(); if (!isConfigurationChangeAcceptable(configuration, messages)) { throw new ConfigException(messages.get(0)); } /* Register change listeners. */ configuration.addAttributeCleanupChangeListener(this); /* Save the configuration. */ applyConfigurationChange(configuration); } /** * {@inheritDoc} */ @Override public boolean isConfigurationAcceptable(final PluginCfg configuration, final List<Message> unacceptableReasons) { final AttributeCleanupPluginCfg cfg = (AttributeCleanupPluginCfg) configuration; return isConfigurationChangeAcceptable(cfg, unacceptableReasons); } /** * {@inheritDoc} */ @Override public boolean isConfigurationChangeAcceptable( final AttributeCleanupPluginCfg config, final List<Message> messages) { /* * The admin framework will ensure that there are no duplicate attributes to * be removed. */ boolean isValid = true; /* * Verify that there are no duplicate mappings and that attributes are * renamed to valid attribute types. */ final Set<String> fromAttrs = new HashSet<String>(); for (final String attr : config.getRenameInboundAttributes()) { /* * The format is: from:to where each 'from' and 'to' are attribute * descriptions. The admin framework ensures that the format is correct. */ final int colonPos = attr.lastIndexOf(":"); final String fromAttr = attr.substring(0, colonPos).trim(); final String toAttr = attr.substring(colonPos + 1).trim(); /* * Make sure that toAttr is defined within the server, being careful to * ignore attribute options. */ final int semicolonPos = toAttr.indexOf(";"); final String toAttrType = (semicolonPos < 0) && (semicolonPos < (toAttr.length() - 1)) ? toAttr : toAttr .substring(semicolonPos + 1); if (DirectoryServer.getAttributeType(toLowerCase(toAttrType)) == null) { messages.add(ERR_PLUGIN_ATTR_CLEANUP_ATTRIBUTE_MISSING.get(toAttr)); isValid = false; } /* * Check for duplicates. */ final String nfromAttr = toLowerCase(fromAttr); if (fromAttrs.contains(nfromAttr)) { messages.add(ERR_PLUGIN_ATTR_CLEANUP_DUPLICATE_VALUE.get(fromAttr)); isValid = false; } else { fromAttrs.add(nfromAttr); } /* * Check that attribute does not map to itself. */ if (nfromAttr.equals(toLowerCase(toAttr))) { messages .add(ERR_PLUGIN_ATTR_CLEANUP_EQUAL_VALUES.get(fromAttr, toAttr)); isValid = false; } } return isValid; } /** * Remove the attributes listed in the configuration under * ds-cfg-remove-inbound-attributes from the incoming ADD request. * * @param addOperation * Current ADD operation. */ private void processInboundRemove(final PreParseAddOperation addOperation) { final List<RawAttribute> inAttrs = new LinkedList<RawAttribute>( addOperation.getRawAttributes()); final ListIterator<RawAttribute> iterator = inAttrs.listIterator(); while (iterator.hasNext()) { final RawAttribute rawAttr = iterator.next(); final String attrName = toLowerCase(rawAttr.getAttributeType().trim()); if (attributesToRemove.contains(attrName)) { if (debugEnabled()) { TRACER.debugVerbose("AttributeCleanupPlugin removing '%s'", rawAttr.getAttributeType()); } iterator.remove(); } } addOperation.setRawAttributes(inAttrs); } /** * Remove the attributes listed in the configuration under * ds-cfg-remove-inbound-attributes from the incoming MODIFY request. * * @param modifyOperation * Current MODIFY operation. */ private void processInboundRemove( final PreParseModifyOperation modifyOperation) { final List<RawModification> rawMods = new LinkedList<RawModification>( modifyOperation.getRawModifications()); final ListIterator<RawModification> iterator = rawMods.listIterator(); while (iterator.hasNext()) { final RawModification rawMod = iterator.next(); final RawAttribute rawAttr = rawMod.getAttribute(); final String attrName = toLowerCase(rawAttr.getAttributeType().trim()); if (attributesToRemove.contains(attrName)) { if (debugEnabled()) { TRACER.debugVerbose("AttributeCleanupPlugin removing '%s'", rawAttr.getAttributeType()); } iterator.remove(); } } modifyOperation.setRawModifications(rawMods); } /** * Map the incoming attributes to the local ones. * * @param addOperation * Current ADD operation. */ private void processInboundRename(final PreParseAddOperation addOperation) { final List<RawAttribute> inAttrs = new LinkedList<RawAttribute>( addOperation.getRawAttributes()); final ListIterator<RawAttribute> iterator = inAttrs.listIterator(); while (iterator.hasNext()) { final RawAttribute rawAttr = iterator.next(); final String fromName = toLowerCase(rawAttr.getAttributeType().trim()); final String toName = attributesToRename.get(fromName); if (toName != null) { if (debugEnabled()) { TRACER.debugVerbose("AttributeCleanupPlugin renaming '%s' to '%s'", rawAttr.getAttributeType(), toName); } rawAttr.setAttributeType(toName); } } addOperation.setRawAttributes(inAttrs); } /** * Rename the attributes in the incoming MODIFY request to names that exist in * the local schema as defined in the configuration. * * @param modifyOperation * Current MODIFY operation. */ private void processInboundRename( final PreParseModifyOperation modifyOperation) { final List<RawModification> rawMods = new LinkedList<RawModification>( modifyOperation.getRawModifications()); final ListIterator<RawModification> iterator = rawMods.listIterator(); while (iterator.hasNext()) { final RawModification rawMod = iterator.next(); final RawAttribute rawAttr = rawMod.getAttribute(); final String fromName = toLowerCase(rawAttr.getAttributeType().trim()); final String toName = attributesToRename.get(fromName); if (toName != null) { if (debugEnabled()) { TRACER.debugVerbose("AttributeCleanupPlugin renaming '%s' to '%s'", rawAttr.getAttributeType(), toName); } rawAttr.setAttributeType(toName); } } modifyOperation.setRawModifications(rawMods); } } opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/AttributeCleanupPluginTestCase.java
New file @@ -0,0 +1,725 @@ /* * 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 * * * Copyright 2011 profiq s.r.o. */ package org.opends.server.plugins; import static org.testng.Assert.*; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import org.opends.server.TestCaseUtils; import org.opends.server.admin.server.AdminTestCaseUtils; import org.opends.server.admin.std.meta.AttributeCleanupPluginCfgDefn; import org.opends.server.admin.std.server.AttributeCleanupPluginCfg; import org.opends.server.api.plugin.PluginResult; import org.opends.server.api.plugin.PluginType; import org.opends.server.config.ConfigException; import org.opends.server.core.AddOperationBasis; import org.opends.server.core.ModifyOperationBasis; import org.opends.server.protocols.internal.InternalClientConnection; import org.opends.server.types.*; import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; /** * Tests for the attribute cleanup plugin. */ public class AttributeCleanupPluginTestCase extends PluginTestCase { @BeforeClass() public void startServer() throws Exception { TestCaseUtils.startServer(); } @DataProvider(name = "validConfigs") public Object[][] getValidConfigs() throws Exception { List<Entry> entries = TestCaseUtils.makeEntries( "dn: cn=Attribute Cleanup,cn=Plugins,cn=config", "objectClass: top", "objectClass: ds-cfg-plugin", "objectClass: ds-cfg-attribute-cleanup-plugin", "cn: Attribute Cleanup", "ds-cfg-enabled: true", "ds-cfg-plugin-type: preparseadd", "ds-cfg-plugin-type: preparsemodify", "ds-cfg-java-class: org.opends.server.plugins.AttributeCleanupPlugin", "", "dn: cn=Attribute Cleanup,cn=Plugins,cn=config", "objectClass: top", "objectClass: ds-cfg-plugin", "objectClass: ds-cfg-attribute-cleanup-plugin", "cn: Attribute Cleanup", "ds-cfg-enabled: true", "ds-cfg-plugin-type: preparseadd", "ds-cfg-plugin-type: preparsemodify", "ds-cfg-java-class: org.opends.server.plugins.AttributeCleanupPlugin", "ds-cfg-remove-inbound-attributes: modifyTimeStamp", "ds-cfg-remove-inbound-attributes: createTimeStamp", "", "dn: cn=Attribute Cleanup,cn=Plugins,cn=config", "objectClass: top", "objectClass: ds-cfg-plugin", "objectClass: ds-cfg-attribute-cleanup-plugin", "cn: Attribute Cleanup", "ds-cfg-enabled: true", "ds-cfg-plugin-type: preparseadd", "ds-cfg-plugin-type: preparsemodify", "ds-cfg-java-class: org.opends.server.plugins.AttributeCleanupPlugin", "ds-cfg-rename-inbound-attributes: cn:uid"); Object[][] array = new Object[entries.size()][1]; for (int i=0; i < array.length; i++) { array[i] = new Object[] { entries.get(i) }; } return array; } @Test(dataProvider = "validConfigs") public void testInitializeWithValidConfigs(Entry e) throws Exception { HashSet<PluginType> pluginTypes = getPluginTypes(e); assertTrue(!pluginTypes.isEmpty()); AttributeCleanupPluginCfg config = AdminTestCaseUtils.getConfiguration( AttributeCleanupPluginCfgDefn.getInstance(),e); assertNotNull(config); AttributeCleanupPlugin plugin = new AttributeCleanupPlugin(); plugin.initializePlugin(pluginTypes, config); plugin.finalizePlugin(); } @DataProvider(name = "invalidConfigs") public Object[][] getInvalidConfigs() throws Exception { List<Entry> entries = TestCaseUtils.makeEntries( /* local attribute is not defined */ "dn: cn=Attribute Cleanup,cn=Plugins,cn=config", "objectClass: top", "objectClass: ds-cfg-plugin", "objectClass: ds-cfg-attribute-cleanup-plugin", "cn: Attribute Cleanup", "ds-cfg-enabled: true", "ds-cfg-plugin-type: preparseadd", "ds-cfg-plugin-type: preparsemodify", "ds-cfg-java-class: org.opends.server.plugins.AttributeCleanupPlugin", "ds-cfg-rename-inbound-attributes: cn:badAttr", "", /* duplicate attributes */ "dn: cn=Attribute Cleanup,cn=Plugins,cn=config", "objectClass: top", "objectClass: ds-cfg-plugin", "objectClass: ds-cfg-attribute-cleanup-plugin", "cn: Attribute Cleanup", "ds-cfg-enabled: true", "ds-cfg-plugin-type: preparseadd", "ds-cfg-plugin-type: preparsemodify", "ds-cfg-java-class: org.opends.server.plugins.AttributeCleanupPlugin", "ds-cfg-rename-inbound-attributes: cn:uid", "ds-cfg-rename-inbound-attributes: cn:description", "", /* self mapping */ "dn: cn=Attribute Cleanup,cn=Plugins,cn=config", "objectClass: top", "objectClass: ds-cfg-plugin", "objectClass: ds-cfg-attribute-cleanup-plugin", "cn: Attribute Cleanup", "ds-cfg-enabled: true", "ds-cfg-plugin-type: preparseadd", "ds-cfg-plugin-type: preparsemodify", "ds-cfg-java-class: org.opends.server.plugins.AttributeCleanupPlugin", "ds-cfg-rename-inbound-attributes: cn:cn"); Object[][] array = new Object[entries.size()][1]; for (int i=0; i < array.length; i++) { array[i] = new Object[] { entries.get(i) }; } return array; } @Test(dataProvider = "invalidConfigs", expectedExceptions = { ConfigException.class } ) public void testInitializeWithInvalidConfigs(Entry e) throws ConfigException, InitializationException { HashSet<PluginType> pluginTypes = new HashSet<PluginType>(); List<Attribute> attrList = e.getAttribute("ds-cfg-plugin-type"); assertNotNull(attrList); for (Attribute attr : attrList) { for (AttributeValue value : attr) { pluginTypes.add( PluginType.forName( value.getValue().toString().toLowerCase())); } } assertTrue(!pluginTypes.isEmpty()); AttributeCleanupPluginCfg config = AdminTestCaseUtils.getConfiguration( AttributeCleanupPluginCfgDefn.getInstance(),e); assertNotNull(config); AttributeCleanupPlugin plugin = new AttributeCleanupPlugin(); plugin.initializePlugin(pluginTypes, config); plugin.finalizePlugin(); } /** * Verifies the attribute renaming in the incoming ADD operation. * * @throws Exception in case of bugs. */ @Test() public void testRenameAttributesForAddOperation() throws Exception { /* Configure the plugint to rename incoming 'cn' attributes to * 'description'. */ Entry confEntry = TestCaseUtils.makeEntry( "dn: cn=Attribute Cleanup,cn=Plugins,cn=config", "objectClass: top", "objectClass: ds-cfg-plugin", "objectClass: ds-cfg-attribute-cleanup-plugin", "cn: Attribute Cleanup", "ds-cfg-enabled: true", "ds-cfg-plugin-type: preparseadd", "ds-cfg-plugin-type: preparsemodify", "ds-cfg-rename-inbound-attributes: cn:description", "ds-cfg-java-class: org.opends.server.plugins.AttributeCleanupPlugin"); HashSet<PluginType> pluginTypes = getPluginTypes(confEntry); AttributeCleanupPluginCfg config = AdminTestCaseUtils.getConfiguration( AttributeCleanupPluginCfgDefn.getInstance(),confEntry); AttributeCleanupPlugin plugin = new AttributeCleanupPlugin(); plugin.initializePlugin(pluginTypes, config); /* Construct the ADD operation as follows: * * dn: uid=test,dc=example,dc=com * objectClass: top * objectClass: person * objectClass: organizationalPerson * objectClass: inetOrgPerson * uid: test * cn: Name Surname * sn: Surname */ ArrayList<ByteString> values = new ArrayList<ByteString>(); values.add(ByteString.valueOf("top")); values.add(ByteString.valueOf("person")); values.add(ByteString.valueOf("organizationalperson")); values.add(ByteString.valueOf("inetorgperson")); List<RawAttribute> rawAttributes = new ArrayList<RawAttribute>(); rawAttributes.add(RawAttribute.create("objectClass", values)); rawAttributes.add(RawAttribute.create("uid", "test")); rawAttributes.add(RawAttribute.create("cn", "Name Surname")); rawAttributes.add(RawAttribute.create("sn", "Surname")); AddOperationBasis addOperation = new AddOperationBasis(InternalClientConnection.getRootConnection(), 1, 1, null, ByteString.valueOf("dn: uid=test,dc=example,dc=com"), rawAttributes); /* Process the operation. The processing should continue. */ PluginResult.PreParse res = plugin.doPreParse(addOperation); assertTrue(res.continueProcessing()); /* Verify that the 'cn' attribute has been renamed to 'description' * by getting the 'decription' attribute and matching the value with * the original 'cn' value. */ List<RawAttribute> rawAttrs = addOperation.getRawAttributes(); assertNotNull(rawAttrs); for(RawAttribute rawAttr : rawAttrs) { if(rawAttr.getAttributeType().equalsIgnoreCase("description")) { ArrayList<ByteString> attrVals = rawAttr.getValues(); assertEquals("Name Surname", attrVals.get(0).toString()); plugin.finalizePlugin(); return; } } fail(); } /** * Verifies the attribute removal in the incoming ADD request. * @throws Exception in case of bugs. */ @Test() public void testRemoveAttributesForAddOperation() throws Exception { /* Configure the plugin to remove 'modifyTimeStamp' and * 'createTimeStamp' attributes from the incoming ADD requests. */ Entry confEntry = TestCaseUtils.makeEntry( "dn: cn=Attribute Cleanup,cn=Plugins,cn=config", "objectClass: top", "objectClass: ds-cfg-plugin", "objectClass: ds-cfg-attribute-cleanup-plugin", "cn: Attribute Cleanup", "ds-cfg-enabled: true", "ds-cfg-plugin-type: preparseadd", "ds-cfg-plugin-type: preparsemodify", "ds-cfg-remove-inbound-attributes: modifyTimeStamp", "ds-cfg-remove-inbound-attributes: createTimeStamp", "ds-cfg-java-class: org.opends.server.plugins.AttributeCleanupPlugin"); HashSet<PluginType> pluginTypes = getPluginTypes(confEntry); AttributeCleanupPluginCfg config = AdminTestCaseUtils.getConfiguration( AttributeCleanupPluginCfgDefn.getInstance(),confEntry); AttributeCleanupPlugin plugin = new AttributeCleanupPlugin(); plugin.initializePlugin(pluginTypes, config); /* Create the ADD operation as follows: * * dn: uid=test,dc=example,dc=com * objectClass: top * objectClass: person * objectClass: organizationalPerson * objectClass: inetOrgPerson * uid: test * cn: Name Surname * sn: Surname * modifyTimeStamp: 2011091212400000Z * createTimeStamp: 2011091212400000Z */ ArrayList<ByteString> values = new ArrayList<ByteString>(); values.add(ByteString.valueOf("top")); values.add(ByteString.valueOf("person")); values.add(ByteString.valueOf("organizationalperson")); values.add(ByteString.valueOf("inetorgperson")); List<RawAttribute> rawAttributes = new ArrayList<RawAttribute>(); rawAttributes.add(RawAttribute.create("objectClass", values)); rawAttributes.add(RawAttribute.create("uid", "test")); rawAttributes.add(RawAttribute.create("cn", "Name Surname")); rawAttributes.add(RawAttribute.create("sn", "Surname")); rawAttributes.add(RawAttribute.create("modifyTimeStamp", "2011091212400000Z")); rawAttributes.add(RawAttribute.create("createTimeStamp", "2011091212400000Z")); AddOperationBasis addOperation = new AddOperationBasis(InternalClientConnection.getRootConnection(), 1, 1, null, ByteString.valueOf("dn: uid=test,dc=example,dc=com"), rawAttributes); /* Process the operation and expect the server to continue * processing the operation. */ PluginResult.PreParse res = plugin.doPreParse(addOperation); assertTrue(res.continueProcessing()); /* Verify that the '*TimeStamp' attributes have been removed. */ List<RawAttribute> rawAttrs = addOperation.getRawAttributes(); assertNotNull(rawAttrs); for(RawAttribute rawAttr : rawAttrs) { if(rawAttr.getAttributeType().equalsIgnoreCase("modifyTimeStamp") || rawAttr.getAttributeType().equalsIgnoreCase("createTimeStamp")) { fail("Attribute '" + rawAttr.getAttributeType() + "' exists and it shouldn't"); } } plugin.finalizePlugin(); } /** * In some cases the plugin might remove all attributes from the * incoming MODIFY request which would make the request invalid by the * LDAP standards. However, this is a special case and the request * should be silently dropped while the client should be notified of * SUCCESS. * @throws Exception in case of a bug. */ @Test() public void testRemoveAttributesForModifyOperationInvalid() throws Exception { /* Configure the plugin to remove 'modifyTimeStamp' and * 'createTimeStamp' attributes from the incoming MODIFY request. */ Entry confEntry = TestCaseUtils.makeEntry( "dn: cn=Attribute Cleanup,cn=Plugins,cn=config", "objectClass: top", "objectClass: ds-cfg-plugin", "objectClass: ds-cfg-attribute-cleanup-plugin", "cn: Attribute Cleanup", "ds-cfg-enabled: true", "ds-cfg-plugin-type: preparseadd", "ds-cfg-plugin-type: preparsemodify", "ds-cfg-remove-inbound-attributes: modifyTimeStamp", "ds-cfg-remove-inbound-attributes: createTimeStamp", "ds-cfg-java-class: org.opends.server.plugins.AttributeCleanupPlugin"); HashSet<PluginType> pluginTypes = getPluginTypes(confEntry); AttributeCleanupPluginCfg config = AdminTestCaseUtils.getConfiguration( AttributeCleanupPluginCfgDefn.getInstance(),confEntry); AttributeCleanupPlugin plugin = new AttributeCleanupPlugin(); plugin.initializePlugin(pluginTypes, config); /* Create the MODIFY request as follows: * * dn: uid=test,dc=example,dc=com * changetype: modify * replace: modifyTimeStamp * modifyTimeStamp: 2011091212400000Z * - * replace: createTimeStamp * createTimeStamp: 2011091212400000Z * - */ List<RawModification> rawMods= new ArrayList<RawModification>(); rawMods.add(RawModification.create(ModificationType.REPLACE, "modifyTimeStamp", "2011091212400000Z")); rawMods.add(RawModification.create(ModificationType.REPLACE, "createTimeStamp", "2011091212400000Z")); ModifyOperationBasis modifyOperation = new ModifyOperationBasis(InternalClientConnection.getRootConnection(), 1, 1, null, ByteString.valueOf("dn: uid=test,dc=example,dc=com"), rawMods); /* Process the request. The result should be SUCCESS and the server * should stop the processing. */ PluginResult.PreParse res = plugin.doPreParse(modifyOperation); assertFalse(res.continueProcessing()); assertTrue(res.getResultCode() == ResultCode.SUCCESS); plugin.finalizePlugin(); } /** * Verify the attribute renaming for a MODIFY operation. * @throws Exception in case of a bug. */ @Test() public void testRemoveAttributesForModifyOperationValid() throws Exception { /* Configure the plugin to remove 'modifyTimeStamp' and * 'createTimeStamp' attributes from the incoming requests. */ Entry confEntry = TestCaseUtils.makeEntry( "dn: cn=Attribute Cleanup,cn=Plugins,cn=config", "objectClass: top", "objectClass: ds-cfg-plugin", "objectClass: ds-cfg-attribute-cleanup-plugin", "cn: Attribute Cleanup", "ds-cfg-enabled: true", "ds-cfg-plugin-type: preparseadd", "ds-cfg-plugin-type: preparsemodify", "ds-cfg-remove-inbound-attributes: modifyTimeStamp", "ds-cfg-remove-inbound-attributes: createTimeStamp", "ds-cfg-java-class: org.opends.server.plugins.AttributeCleanupPlugin"); HashSet<PluginType> pluginTypes = getPluginTypes(confEntry); AttributeCleanupPluginCfg config = AdminTestCaseUtils.getConfiguration( AttributeCleanupPluginCfgDefn.getInstance(),confEntry); AttributeCleanupPlugin plugin = new AttributeCleanupPlugin(); plugin.initializePlugin(pluginTypes, config); /* Create the MODIFY operation as follows: * * dn: uid=test,dc=example,dc=com * changetype: modify * replace: cn * cn: Test User * - * replace: sn * sn: User * - * replace: modifyTimeStamp * modifyTimeStamp: 2011091212400000Z * - * replace: createTimeStamp * createTimeStamp: 2011091212400000Z * - */ List<RawModification> rawMods= new ArrayList<RawModification>(); rawMods.add(RawModification.create(ModificationType.REPLACE, "cn", "Test User")); rawMods.add(RawModification.create(ModificationType.REPLACE, "sn", "User")); rawMods.add(RawModification.create(ModificationType.REPLACE, "modifyTimeStamp", "2011091212400000Z")); rawMods.add(RawModification.create(ModificationType.REPLACE, "createTimeStamp", "2011091212400000Z")); ModifyOperationBasis modifyOperation = new ModifyOperationBasis(InternalClientConnection.getRootConnection(), 1, 1, null, ByteString.valueOf("dn: uid=test,dc=example,dc=com"), rawMods); /* Process the MODIFY operation making sure the remaining number of * modifications is 2 and that the '*TimeStamp' modifications are * removed. */ PluginResult.PreParse res = plugin.doPreParse(modifyOperation); assertTrue(modifyOperation.getRawModifications().size() == 2); rawMods = modifyOperation.getRawModifications(); assertNotNull(rawMods); for(RawModification rawMod : rawMods ) { RawAttribute modAttr = rawMod.getAttribute(); if(modAttr.getAttributeType().equalsIgnoreCase("modifyTimeStamp") || modAttr.getAttributeType().equalsIgnoreCase("createTimeStamp")) { fail("Attribute '" + modAttr.getAttributeType() + "' exists and it shouldn't"); } } plugin.finalizePlugin(); } /** * Verify the attribute renaming for the MODIFY operation. * @throws Exception */ @Test() public void testRenameAttributesForModifyOperation() throws Exception { /* Configure the plugin to rename the 'modifyTimeStamp' attribute to * 'description'. */ Entry confEntry = TestCaseUtils.makeEntry( "dn: cn=Attribute Cleanup,cn=Plugins,cn=config", "objectClass: top", "objectClass: ds-cfg-plugin", "objectClass: ds-cfg-attribute-cleanup-plugin", "cn: Attribute Cleanup", "ds-cfg-enabled: true", "ds-cfg-plugin-type: preparseadd", "ds-cfg-plugin-type: preparsemodify", "ds-cfg-rename-inbound-attributes: modifyTimeStamp:description", "ds-cfg-java-class: org.opends.server.plugins.AttributeCleanupPlugin"); HashSet<PluginType> pluginTypes = getPluginTypes(confEntry); AttributeCleanupPluginCfg config = AdminTestCaseUtils.getConfiguration( AttributeCleanupPluginCfgDefn.getInstance(),confEntry); AttributeCleanupPlugin plugin = new AttributeCleanupPlugin(); plugin.initializePlugin(pluginTypes, config); /* Create the MODIFY operation as follows: * * dn: uid=test,dc=exampple,dc=com * changetype: modify * replace: cn * cn: Test User * - * replace: sn * sn: User * - * replace: modifyTimeStamp * modifyTimeStamp: 2011091212400000Z */ List<RawModification> rawMods= new ArrayList<RawModification>(); rawMods.add(RawModification.create(ModificationType.REPLACE, "cn", "Test User")); rawMods.add(RawModification.create(ModificationType.REPLACE, "sn", "User")); rawMods.add(RawModification.create(ModificationType.REPLACE, "modifyTimeStamp", "2011091212400000Z")); ModifyOperationBasis modifyOperation = new ModifyOperationBasis(InternalClientConnection.getRootConnection(), 1, 1, null, ByteString.valueOf("dn: uid=test,dc=example,dc=com"), rawMods); /* Process the MODIFY operation. */ PluginResult.PreParse res = plugin.doPreParse(modifyOperation); assertTrue(res.continueProcessing()); /* Verify that the attribute has been properly renamed by comparing * the value of the attribute 'description' with the original value * of the 'modifyTimeStamp' attribute. */ rawMods = modifyOperation.getRawModifications(); assertNotNull(rawMods); for(RawModification rawMod : rawMods ) { RawAttribute modAttr = rawMod.getAttribute(); if (modAttr.getAttributeType().equalsIgnoreCase("description")) { ArrayList<ByteString> descrValues = modAttr.getValues(); assertEquals("2011091212400000Z", descrValues.get(0).toString()); plugin.finalizePlugin(); return; } if (modAttr.getAttributeType().equalsIgnoreCase("modifyTimeStamp")) { fail("modifyTimeStamp shouldn't exist but it does."); } } fail(); } /** * Helper method to get the plugin types from the configuration entry. * @param e Configuration entry. * @return HashSet of plugin types. */ private HashSet<PluginType> getPluginTypes(Entry e) { HashSet<PluginType> pluginTypes = new HashSet<PluginType>(); List<Attribute> attrList = e.getAttribute("ds-cfg-plugin-type"); for(Attribute a : attrList) { for (AttributeValue v : a) { pluginTypes.add( PluginType.forName(v.getValue().toString().toLowerCase())); } } return pluginTypes; } }