From b1e3b0ccdaa423b68ef6fa2fee67d3e09990985f Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Mon, 24 Sep 2007 23:15:46 +0000
Subject: [PATCH] Perform a significant refactoring of the local backend workflow element classes so that they use smaller method sizes, which allows the VM to better optimize the code. Also, move the code for processing each type of operation into the class for the associated operation rather than keeping it all in the LocalBackendWorkflowElement class.
---
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java | 2321 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 2,264 insertions(+), 57 deletions(-)
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
index 2339824..4caefd9 100644
--- a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
+++ b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
@@ -26,41 +26,172 @@
*/
package org.opends.server.workflowelement.localbackend;
-import java.util.List;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.locks.Lock;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.server.api.AttributeSyntax;
+import org.opends.server.api.Backend;
+import org.opends.server.api.ChangeNotificationListener;
+import org.opends.server.api.ClientConnection;
+import org.opends.server.api.PasswordStorageScheme;
+import org.opends.server.api.SynchronizationProvider;
+import org.opends.server.api.plugin.PostOperationPluginResult;
+import org.opends.server.api.plugin.PreOperationPluginResult;
+import org.opends.server.controls.LDAPAssertionRequestControl;
+import org.opends.server.controls.LDAPPostReadRequestControl;
+import org.opends.server.controls.LDAPPostReadResponseControl;
+import org.opends.server.controls.LDAPPreReadRequestControl;
+import org.opends.server.controls.LDAPPreReadResponseControl;
+import org.opends.server.controls.PasswordPolicyErrorType;
+import org.opends.server.controls.PasswordPolicyResponseControl;
+import org.opends.server.controls.ProxiedAuthV1Control;
+import org.opends.server.controls.ProxiedAuthV2Control;
+import org.opends.server.core.AccessControlConfigManager;
+import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ModifyOperation;
import org.opends.server.core.ModifyOperationWrapper;
+import org.opends.server.core.PasswordPolicyState;
+import org.opends.server.core.PluginConfigManager;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.protocols.asn1.ASN1OctetString;
+import org.opends.server.schema.AuthPasswordSyntax;
+import org.opends.server.schema.BooleanSyntax;
+import org.opends.server.schema.UserPasswordSyntax;
+import org.opends.server.types.AccountStatusNotification;
+import org.opends.server.types.AccountStatusNotificationType;
+import org.opends.server.types.AcceptRejectWarn;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
+import org.opends.server.types.AuthenticationInfo;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.CancelledOperationException;
+import org.opends.server.types.CancelResult;
+import org.opends.server.types.Control;
+import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DirectoryException;
+import org.opends.server.types.DN;
import org.opends.server.types.Entry;
+import org.opends.server.types.LDAPException;
+import org.opends.server.types.LockManager;
import org.opends.server.types.Modification;
+import org.opends.server.types.ModificationType;
+import org.opends.server.types.Privilege;
+import org.opends.server.types.RDN;
+import org.opends.server.types.ResultCode;
+import org.opends.server.types.SearchFilter;
+import org.opends.server.types.SearchResultEntry;
+import org.opends.server.types.SynchronizationProviderResult;
import org.opends.server.types.operation.PostOperationModifyOperation;
import org.opends.server.types.operation.PostResponseModifyOperation;
import org.opends.server.types.operation.PreOperationModifyOperation;
import org.opends.server.types.operation.PostSynchronizationModifyOperation;
+import static org.opends.messages.CoreMessages.*;
+import static org.opends.server.config.ConfigConstants.*;
+import static org.opends.server.loggers.ErrorLogger.*;
+import static org.opends.server.loggers.debug.DebugLogger.*;
+import static org.opends.server.util.ServerConstants.*;
+import static org.opends.server.util.StaticUtils.*;
+
+
+
/**
* This class defines an operation used to modify an entry in a local backend
* of the Directory Server.
*/
-public class LocalBackendModifyOperation extends ModifyOperationWrapper
- implements PreOperationModifyOperation,
- PostOperationModifyOperation,
- PostResponseModifyOperation,
- PostSynchronizationModifyOperation
+public class LocalBackendModifyOperation
+ extends ModifyOperationWrapper
+ implements PreOperationModifyOperation, PostOperationModifyOperation,
+ PostResponseModifyOperation,
+ PostSynchronizationModifyOperation
{
+ /**
+ * The tracer object for the debug logger.
+ */
+ private static final DebugTracer TRACER = getTracer();
+
+
+
+ // The backend in which the target entry exists.
+ private Backend backend;
+
+ // Indicates whether the request included the user's current password.
+ private boolean currentPasswordProvided;
+
+ // Indicates whether the user's account has been enabled or disabled by this
+ // modify operation.
+ private boolean enabledStateChanged;
+
+ // Indicates whether the user's account is currently enabled.
+ private boolean isEnabled;
+
+ // Indicates whether the request included the LDAP no-op control.
+ private boolean noOp;
+
+ // Indicates whether this modify operation includees a password change.
+ private boolean passwordChanged;
+
+ // Indicates whether the request included the password policy request control.
+ private boolean pwPolicyControlRequested;
+
+ // Indicates whether the password change is a self-change.
+ private boolean selfChange;
+
+ // Indicates whether to skip post-operation plugin processing.
+ private boolean skipPostOperation;
+
+ // Indicates whether the user's account was locked before this change.
+ private boolean wasLocked;
+
+ // The client connection associated with this operation.
+ private ClientConnection clientConnection;
+
+ // The DN of the entry to modify.
+ private DN entryDN;
+
// The current entry, before any changes are applied.
private Entry currentEntry = null;
// The modified entry that will be stored in the backend.
private Entry modifiedEntry = null;
+ // The number of passwords contained in the modify operation.
+ private int numPasswords;
+
+ // The post-read request control, if present.
+ private LDAPPostReadRequestControl postReadRequest;
+
+ // The pre-read request control, if present.
+ private LDAPPreReadRequestControl preReadRequest;
+
// The set of clear-text current passwords (if any were provided).
private List<AttributeValue> currentPasswords = null;
// The set of clear-text new passwords (if any were provided).
private List<AttributeValue> newPasswords = null;
+ // The set of modifications contained in this request.
+ private List<Modification> modifications;
+
+ // The password policy error type for this operation.
+ private PasswordPolicyErrorType pwpErrorType;
+
+ // The password policy state for this modify operation.
+ private PasswordPolicyState pwPolicyState;
+
+
+
/**
* Creates a new operation that may be used to modify an entry in a
* local backend of the Directory Server.
@@ -73,6 +204,8 @@
LocalBackendWorkflowElement.attachLocalOperation (modify, this);
}
+
+
/**
* Retrieves the current entry before any modifications are applied. This
* will not be available to pre-parse plugins.
@@ -85,6 +218,8 @@
return currentEntry;
}
+
+
/**
* Retrieves the set of clear-text current passwords for the user, if
* available. This will only be available if the modify operation contains
@@ -101,6 +236,8 @@
return currentPasswords;
}
+
+
/**
* Retrieves the modified entry that is to be written to the backend. This
* will be available to pre-operation plugins, and if such a plugin does make
@@ -115,6 +252,8 @@
return modifiedEntry;
}
+
+
/**
* Retrieves the set of clear-text new passwords for the user, if available.
* This will only be available if the modify operation contains one or more
@@ -130,57 +269,6 @@
return newPasswords;
}
- /**
- * Retrieves the current entry before any modifications are applied. This
- * will not be available to pre-parse plugins.
- *
- * @param currentEntry The current entry.
- */
- public final void setCurrentEntry(Entry currentEntry)
- {
- this.currentEntry = currentEntry;
- }
-
- /**
- * Register the set of clear-text current passwords for the user, if
- * available. This will only be available if the modify operation contains
- * one or more delete elements that target the password attribute and provide
- * the values to delete in the clear.
- *
- * @param currentPasswords The set of clear-text current password values as
- * provided in the modify request.
- */
- public final void setCurrentPasswords(List<AttributeValue> currentPasswords)
- {
- this.currentPasswords = currentPasswords;
- }
-
- /**
- * Register the modified entry that is to be written to the backend.
- *
- * @param modifiedEntry The modified entry that is to be written to the
- * backend, or <CODE>null</CODE> if it is not yet
- * available.
- */
- public final void setModifiedEntry(Entry modifiedEntry)
- {
- this.modifiedEntry = modifiedEntry;
- }
-
- /**
- * Register the set of clear-text new passwords for the user, if available.
- * This will only be available if the modify operation contains one or more
- * add or replace elements that target the password attribute and provide the
- * values in the clear.
- *
- * @param newPasswords The set of clear-text new passwords as provided in
- * the modify request, or <CODE>null</CODE> if there
- * were none or this information is not yet available.
- */
- public final void setNewPasswords(List<AttributeValue> newPasswords)
- {
- this.newPasswords = newPasswords;
- }
/**
@@ -202,4 +290,2123 @@
modifiedEntry.applyModification(modification);
super.addModification(modification);
}
+
+
+
+ /**
+ * Process this modify operation against a local backend.
+ *
+ * @param backend The backend in which the modify operation should be
+ * performed.
+ */
+ void processLocalModify(Backend backend)
+ {
+ this.backend = backend;
+
+ clientConnection = getClientConnection();
+
+ // Get the plugin config manager that will be used for invoking plugins.
+ PluginConfigManager pluginConfigManager =
+ DirectoryServer.getPluginConfigManager();
+ skipPostOperation = false;
+
+ // Create a labeled block of code that we can break out of if a problem is
+ // detected.
+modifyProcessing:
+ {
+ entryDN = getEntryDN();
+ if (entryDN == null){
+ break modifyProcessing;
+ }
+
+ // Process the modifications to convert them from their raw form to the
+ // form required for the rest of the modify processing.
+ modifications = getModifications();
+ if (modifications == null)
+ {
+ break modifyProcessing;
+ }
+
+ if (modifications.isEmpty())
+ {
+ setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+ appendErrorMessage(ERR_MODIFY_NO_MODIFICATIONS.get(
+ String.valueOf(entryDN)));
+ break modifyProcessing;
+ }
+
+
+ // If the user must change their password before doing anything else, and
+ // if the target of the modify operation isn't the user's own entry, then
+ // reject the request.
+ if ((! isInternalOperation()) && clientConnection.mustChangePassword())
+ {
+ DN authzDN = getAuthorizationDN();
+ if ((authzDN != null) && (! authzDN.equals(entryDN)))
+ {
+ // The user will not be allowed to do anything else before the
+ // password gets changed. Also note that we haven't yet checked the
+ // request controls so we need to do that now to see if the password
+ // policy request control was provided.
+ for (Control c : getRequestControls())
+ {
+ if (c.getOID().equals(OID_PASSWORD_POLICY_CONTROL))
+ {
+ pwPolicyControlRequested = true;
+ pwpErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET;
+ break;
+ }
+ }
+
+ setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+ appendErrorMessage(ERR_MODIFY_MUST_CHANGE_PASSWORD.get());
+ break modifyProcessing;
+ }
+ }
+
+
+ // Check for a request to cancel this operation.
+ if (getCancelRequest() != null)
+ {
+ return;
+ }
+
+ // Acquire a write lock on the target entry.
+ Lock entryLock = null;
+ for (int i=0; i < 3; i++)
+ {
+ entryLock = LockManager.lockWrite(entryDN);
+ if (entryLock != null)
+ {
+ break;
+ }
+ }
+
+ if (entryLock == null)
+ {
+ setResultCode(DirectoryServer.getServerErrorResultCode());
+ appendErrorMessage(ERR_MODIFY_CANNOT_LOCK_ENTRY.get(
+ String.valueOf(entryDN)));
+ skipPostOperation = true;
+ break modifyProcessing;
+ }
+
+
+ try
+ {
+ // Check for a request to cancel this operation.
+ if (getCancelRequest() != null)
+ {
+ return;
+ }
+
+
+ // Get the entry to modify. If it does not exist, then fail.
+ try
+ {
+ currentEntry = backend.getEntry(entryDN);
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ setResponseData(de);
+ break modifyProcessing;
+ }
+
+ if (currentEntry == null)
+ {
+ setResultCode(ResultCode.NO_SUCH_OBJECT);
+ appendErrorMessage(ERR_MODIFY_NO_SUCH_ENTRY.get(
+ String.valueOf(entryDN)));
+
+ // See if one of the entry's ancestors exists.
+ DN parentDN = entryDN.getParentDNInSuffix();
+ while (parentDN != null)
+ {
+ try
+ {
+ if (DirectoryServer.entryExists(parentDN))
+ {
+ setMatchedDN(parentDN);
+ break;
+ }
+ }
+ catch (Exception e)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+ break;
+ }
+
+ parentDN = parentDN.getParentDNInSuffix();
+ }
+
+ break modifyProcessing;
+ }
+
+
+ // Check to see if there are any controls in the request. If so, then
+ // see if there is any special processing required.
+ try
+ {
+ processRequestControls();
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ setResponseData(de);
+ break modifyProcessing;
+ }
+
+
+ // Get the password policy state object for the entry that can be used
+ // to perform any appropriate password policy processing. Also, see if
+ // the entry is being updated by the end user or an administrator.
+ selfChange = entryDN.equals(getAuthorizationDN());
+ try
+ {
+ // FIXME -- Need a way to enable debug mode.
+ pwPolicyState = new PasswordPolicyState(currentEntry, false, false);
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ setResultCode(de.getResultCode());
+ appendErrorMessage(de.getMessageObject());
+ break modifyProcessing;
+ }
+
+
+ // Create a duplicate of the entry and apply the changes to it.
+ modifiedEntry = currentEntry.duplicate(false);
+
+ if (! noOp)
+ {
+ // Invoke any conflict resolution processing that might be needed by
+ // the synchronization provider.
+ for (SynchronizationProvider provider :
+ DirectoryServer.getSynchronizationProviders())
+ {
+ try
+ {
+ SynchronizationProviderResult result =
+ provider.handleConflictResolution(this);
+ if (! result.continueOperationProcessing())
+ {
+ break modifyProcessing;
+ }
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ logError(ERR_MODIFY_SYNCH_CONFLICT_RESOLUTION_FAILED.get(
+ getConnectionID(), getOperationID(),
+ getExceptionMessage(de)));
+ setResponseData(de);
+ break modifyProcessing;
+ }
+ }
+ }
+
+ try
+ {
+ handleInitialPasswordPolicyAndSchemaProcessing();
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ setResponseData(de);
+ break modifyProcessing;
+ }
+
+
+ // If there was a password change, then perform any additional checks
+ // that may be necessary.
+ wasLocked = false;
+ if (passwordChanged)
+ {
+ try
+ {
+ performAdditionalPasswordChangedProcessing();
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ setResponseData(de);
+ break modifyProcessing;
+ }
+ }
+
+
+ // Check to see if the client has permission to perform the modify.
+ // The access control check is not made any earlier because the handler
+ // needs access to the modified entry.
+
+ // FIXME: for now assume that this will check all permissions
+ // pertinent to the operation. This includes proxy authorization
+ // and any other controls specified.
+
+ // FIXME: earlier checks to see if the entry already exists may have
+ // already exposed sensitive information to the client.
+ if (! AccessControlConfigManager.getInstance().
+ getAccessControlHandler().isAllowed(this))
+ {
+ setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+ appendErrorMessage(ERR_MODIFY_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
+ String.valueOf(entryDN)));
+ skipPostOperation = true;
+ break modifyProcessing;
+ }
+
+ if ((! passwordChanged) && (! isInternalOperation()) &&
+ pwPolicyState.mustChangePassword())
+ {
+ // The user will not be allowed to do anything else before the
+ // password gets changed.
+ pwpErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET;
+ setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+ appendErrorMessage(ERR_MODIFY_MUST_CHANGE_PASSWORD.get());
+ break modifyProcessing;
+ }
+
+
+ // If the server is configured to check the schema and the
+ // operation is not a sycnhronization operation,
+ // make sure that the new entry is valid per the server schema.
+ if ((DirectoryServer.checkSchema()) && (! isSynchronizationOperation()))
+ {
+ MessageBuilder invalidReason = new MessageBuilder();
+ if (! modifiedEntry.conformsToSchema(null, false, false, false,
+ invalidReason))
+ {
+ setResultCode(ResultCode.OBJECTCLASS_VIOLATION);
+ appendErrorMessage(ERR_MODIFY_VIOLATES_SCHEMA.get(
+ String.valueOf(entryDN), invalidReason));
+ break modifyProcessing;
+ }
+ }
+
+
+ // Check for a request to cancel this operation.
+ if (getCancelRequest() != null)
+ {
+ return;
+ }
+
+ // If the operation is not a synchronization operation,
+ // Invoke the pre-operation modify plugins.
+ if (! isSynchronizationOperation())
+ {
+ PreOperationPluginResult preOpResult =
+ pluginConfigManager.invokePreOperationModifyPlugins(this);
+ if (preOpResult.connectionTerminated())
+ {
+ // There's no point in continuing with anything. Log the result
+ // and return.
+ setResultCode(ResultCode.CANCELED);
+ appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get());
+ setProcessingStopTime();
+ return;
+ }
+ else if (preOpResult.sendResponseImmediately())
+ {
+ skipPostOperation = true;
+ break modifyProcessing;
+ }
+ else if (preOpResult.skipCoreProcessing())
+ {
+ skipPostOperation = false;
+ break modifyProcessing;
+ }
+ }
+
+
+ // Check for a request to cancel this operation.
+ if (getCancelRequest() != null)
+ {
+ return;
+ }
+
+
+ // Actually perform the modify operation. This should also include
+ // taking care of any synchronization that might be needed.
+ if (backend == null)
+ {
+ setResultCode(ResultCode.NO_SUCH_OBJECT);
+ appendErrorMessage(ERR_MODIFY_NO_BACKEND_FOR_ENTRY.get(
+ String.valueOf(entryDN)));
+ break modifyProcessing;
+ }
+
+ try
+ {
+ try
+ {
+ checkWritability();
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ setResponseData(de);
+ break modifyProcessing;
+ }
+
+
+ if (noOp)
+ {
+ appendErrorMessage(INFO_MODIFY_NOOP.get());
+ setResultCode(ResultCode.NO_OPERATION);
+ }
+ else
+ {
+ for (SynchronizationProvider provider :
+ DirectoryServer.getSynchronizationProviders())
+ {
+ try
+ {
+ SynchronizationProviderResult result =
+ provider.doPreOperation(this);
+ if (! result.continueOperationProcessing())
+ {
+ break modifyProcessing;
+ }
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ logError(ERR_MODIFY_SYNCH_PREOP_FAILED.get(getConnectionID(),
+ getOperationID(), getExceptionMessage(de)));
+ setResponseData(de);
+ break modifyProcessing;
+ }
+ }
+
+ backend.replaceEntry(modifiedEntry, this);
+
+
+
+ // See if we need to generate any account status notifications as a
+ // result of the changes.
+ if (passwordChanged || enabledStateChanged || wasLocked)
+ {
+ handleAccountStatusNotifications();
+ }
+ }
+
+
+ // Handle any processing that may be needed for the pre-read and/or
+ // post-read controls.
+ handleReadEntryProcessing();
+
+
+ if (! noOp)
+ {
+ setResultCode(ResultCode.SUCCESS);
+ }
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ setResponseData(de);
+ break modifyProcessing;
+ }
+ catch (CancelledOperationException coe)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, coe);
+ }
+
+ CancelResult cancelResult = coe.getCancelResult();
+
+ setCancelResult(cancelResult);
+ setResultCode(cancelResult.getResultCode());
+
+ Message message = coe.getMessageObject();
+ if ((message != null) && (message.length() > 0))
+ {
+ appendErrorMessage(message);
+ }
+ break modifyProcessing;
+ }
+ }
+ finally
+ {
+ LockManager.unlock(entryDN, entryLock);
+
+ for (SynchronizationProvider provider :
+ DirectoryServer.getSynchronizationProviders())
+ {
+ try
+ {
+ provider.doPostOperation(this);
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ logError(ERR_MODIFY_SYNCH_POSTOP_FAILED.get(getConnectionID(),
+ getOperationID(), getExceptionMessage(de)));
+ setResponseData(de);
+ break;
+ }
+ }
+ }
+ }
+
+
+ // If the password policy request control was included, then make sure we
+ // send the corresponding response control.
+ if (pwPolicyControlRequested)
+ {
+ addResponseControl(new PasswordPolicyResponseControl(null, 0,
+ pwpErrorType));
+ }
+
+
+ // Indicate that it is now too late to attempt to cancel the operation.
+ setCancelResult(CancelResult.TOO_LATE);
+
+ // Invoke the post-operation or post-synchronization modify plugins.
+ if (isSynchronizationOperation())
+ {
+ if (getResultCode() == ResultCode.SUCCESS)
+ {
+ pluginConfigManager.invokePostSynchronizationModifyPlugins(this);
+ }
+ }
+ else if (! skipPostOperation)
+ {
+ // FIXME -- Should this also be done while holding the locks?
+ PostOperationPluginResult postOpResult =
+ pluginConfigManager.invokePostOperationModifyPlugins(this);
+ if (postOpResult.connectionTerminated())
+ {
+ // There's no point in continuing with anything. Log the result and
+ // return.
+ setResultCode(ResultCode.CANCELED);
+ appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get());
+ setProcessingStopTime();
+ return;
+ }
+ }
+
+ // Notify any change notification listeners that might be registered with
+ // the server.
+ if (getResultCode() == ResultCode.SUCCESS)
+ {
+ for (ChangeNotificationListener changeListener :
+ DirectoryServer.getChangeNotificationListeners())
+ {
+ try
+ {
+ changeListener.handleModifyOperation(this, currentEntry,
+ modifiedEntry);
+ }
+ catch (Exception e)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+
+ Message message = ERR_MODIFY_ERROR_NOTIFYING_CHANGE_LISTENER.get(
+ getExceptionMessage(e));
+ logError(message);
+ }
+ }
+ }
+
+
+
+ // Stop the processing timer.
+ setProcessingStopTime();
+ }
+
+
+
+ /**
+ * Processes any controls contained in the modify request.
+ *
+ * @throws DirectoryException If a problem is encountered with any of the
+ * controls.
+ */
+ private void processRequestControls()
+ throws DirectoryException
+ {
+ List<Control> requestControls = getRequestControls();
+ if ((requestControls != null) && (! requestControls.isEmpty()))
+ {
+ for (int i=0; i < requestControls.size(); i++)
+ {
+ Control c = requestControls.get(i);
+ String oid = c.getOID();
+
+ if (! AccessControlConfigManager.getInstance().
+ getAccessControlHandler().isAllowed(entryDN, this, c))
+ {
+ skipPostOperation = true;
+ throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
+ ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
+ }
+
+
+ if (oid.equals(OID_LDAP_ASSERTION))
+ {
+ LDAPAssertionRequestControl assertControl;
+ if (c instanceof LDAPAssertionRequestControl)
+ {
+ assertControl = (LDAPAssertionRequestControl) c;
+ }
+ else
+ {
+ try
+ {
+ assertControl = LDAPAssertionRequestControl.decodeControl(c);
+ requestControls.set(i, assertControl);
+ }
+ catch (LDAPException le)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, le);
+ }
+
+ throw new DirectoryException(
+ ResultCode.valueOf(le.getResultCode()),
+ le.getMessageObject());
+ }
+ }
+
+ try
+ {
+ // FIXME -- We need to determine whether the current user has
+ // permission to make this determination.
+ SearchFilter filter = assertControl.getSearchFilter();
+ if (! filter.matchesEntry(currentEntry))
+ {
+ throw new DirectoryException(ResultCode.ASSERTION_FAILED,
+ ERR_MODIFY_ASSERTION_FAILED.get(
+ String.valueOf(entryDN)));
+ }
+ }
+ catch (DirectoryException de)
+ {
+ if (de.getResultCode() == ResultCode.ASSERTION_FAILED)
+ {
+ throw de;
+ }
+
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
+ ERR_MODIFY_CANNOT_PROCESS_ASSERTION_FILTER.get(
+ String.valueOf(entryDN),
+ de.getMessageObject()));
+ }
+ }
+ else if (oid.equals(OID_LDAP_NOOP_OPENLDAP_ASSIGNED))
+ {
+ noOp = true;
+ }
+ else if (oid.equals(OID_LDAP_READENTRY_PREREAD))
+ {
+ if (c instanceof LDAPPreReadRequestControl)
+ {
+ preReadRequest = (LDAPPreReadRequestControl) c;
+ }
+ else
+ {
+ try
+ {
+ preReadRequest = LDAPPreReadRequestControl.decodeControl(c);
+ requestControls.set(i, preReadRequest);
+ }
+ catch (LDAPException le)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, le);
+ }
+
+ throw new DirectoryException(
+ ResultCode.valueOf(le.getResultCode()),
+ le.getMessageObject());
+ }
+ }
+ }
+ else if (oid.equals(OID_LDAP_READENTRY_POSTREAD))
+ {
+ if (c instanceof LDAPPostReadRequestControl)
+ {
+ postReadRequest = (LDAPPostReadRequestControl) c;
+ }
+ else
+ {
+ try
+ {
+ postReadRequest = LDAPPostReadRequestControl.decodeControl(c);
+ requestControls.set(i, postReadRequest);
+ }
+ catch (LDAPException le)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, le);
+ }
+
+ throw new DirectoryException(
+ ResultCode.valueOf(le.getResultCode()),
+ le.getMessageObject());
+ }
+ }
+ }
+ else if (oid.equals(OID_PROXIED_AUTH_V1))
+ {
+ // The requester must have the PROXIED_AUTH privilige in order to
+ // be able to use this control.
+ if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
+ {
+ throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
+ ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
+ }
+
+
+ ProxiedAuthV1Control proxyControl;
+ if (c instanceof ProxiedAuthV1Control)
+ {
+ proxyControl = (ProxiedAuthV1Control) c;
+ }
+ else
+ {
+ try
+ {
+ proxyControl = ProxiedAuthV1Control.decodeControl(c);
+ }
+ catch (LDAPException le)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, le);
+ }
+
+ throw new DirectoryException(
+ ResultCode.valueOf(le.getResultCode()),
+ le.getMessageObject());
+ }
+ }
+
+
+ Entry authorizationEntry = proxyControl.getAuthorizationEntry();
+ setAuthorizationEntry(authorizationEntry);
+ if (authorizationEntry == null)
+ {
+ setProxiedAuthorizationDN(DN.nullDN());
+ }
+ else
+ {
+ setProxiedAuthorizationDN(authorizationEntry.getDN());
+ }
+ }
+ else if (oid.equals(OID_PROXIED_AUTH_V2))
+ {
+ // The requester must have the PROXIED_AUTH privilige in order to
+ // be able to use this control.
+ if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
+ {
+ throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
+ ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
+ }
+
+
+ ProxiedAuthV2Control proxyControl;
+ if (c instanceof ProxiedAuthV2Control)
+ {
+ proxyControl = (ProxiedAuthV2Control) c;
+ }
+ else
+ {
+ try
+ {
+ proxyControl = ProxiedAuthV2Control.decodeControl(c);
+ }
+ catch (LDAPException le)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, le);
+ }
+
+ throw new DirectoryException(
+ ResultCode.valueOf(le.getResultCode()),
+ le.getMessageObject());
+ }
+ }
+
+
+ Entry authorizationEntry = proxyControl.getAuthorizationEntry();
+ setAuthorizationEntry(authorizationEntry);
+ if (authorizationEntry == null)
+ {
+ setProxiedAuthorizationDN(DN.nullDN());
+ }
+ else
+ {
+ setProxiedAuthorizationDN(authorizationEntry.getDN());
+ }
+ }
+ else if (oid.equals(OID_PASSWORD_POLICY_CONTROL))
+ {
+ pwPolicyControlRequested = true;
+ }
+
+ // NYI -- Add support for additional controls.
+ else if (c.isCritical())
+ {
+ if ((backend == null) || (! backend.supportsControl(oid)))
+ {
+ throw new DirectoryException(
+ ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
+ ERR_MODIFY_UNSUPPORTED_CRITICAL_CONTROL.get(
+ String.valueOf(entryDN), oid));
+ }
+ }
+ }
+ }
+ }
+
+
+
+ /**
+ * Handles the initial set of password policy and schema processing for this
+ * modify operation.
+ *
+ * @throws DirectoryException If a problem is encountered that should cause
+ * the modify operation to fail.
+ */
+ private void handleInitialPasswordPolicyAndSchemaProcessing()
+ throws DirectoryException
+ {
+ // Declare variables used for password policy state processing.
+ currentPasswordProvided = false;
+ isEnabled = true;
+ enabledStateChanged = false;
+ if (currentEntry.hasAttribute(
+ pwPolicyState.getPolicy().getPasswordAttribute()))
+ {
+ // It may actually have more than one, but we can't tell the difference if
+ // the values are encoded, and its enough for our purposes just to know
+ // that there is at least one.
+ numPasswords = 1;
+ }
+ else
+ {
+ numPasswords = 0;
+ }
+
+
+ // If it's not an internal or synchronization operation, then iterate
+ // through the set of modifications to see if a password is included in the
+ // changes. If so, then add the appropriate state changes to the set of
+ // modifications.
+ if (! (isInternalOperation() || isSynchronizationOperation()))
+ {
+ for (Modification m : modifications)
+ {
+ if (m.getAttribute().getAttributeType().equals(
+ pwPolicyState.getPolicy().getPasswordAttribute()))
+ {
+ passwordChanged = true;
+ if (! selfChange)
+ {
+ if (! clientConnection.hasPrivilege(Privilege.PASSWORD_RESET, this))
+ {
+ pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
+ throw new DirectoryException(
+ ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
+ ERR_MODIFY_PWRESET_INSUFFICIENT_PRIVILEGES.get());
+ }
+ }
+
+ break;
+ }
+ }
+ }
+
+
+ for (Modification m : modifications)
+ {
+ Attribute a = m.getAttribute();
+ AttributeType t = a.getAttributeType();
+
+
+ // If the attribute type is marked "NO-USER-MODIFICATION" then fail unless
+ // this is an internal operation or is related to synchronization in some
+ // way.
+ if (t.isNoUserModification())
+ {
+ if (! (isInternalOperation() || isSynchronizationOperation() ||
+ m.isInternal()))
+ {
+ throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+ ERR_MODIFY_ATTR_IS_NO_USER_MOD.get(
+ String.valueOf(entryDN), a.getName()));
+ }
+ }
+
+ // If the attribute type is marked "OBSOLETE" and the modification is
+ // setting new values, then fail unless this is an internal operation or
+ // is related to synchronization in some way.
+ if (t.isObsolete())
+ {
+ if (a.hasValue() &&
+ (m.getModificationType() != ModificationType.DELETE))
+ {
+ if (! (isInternalOperation() || isSynchronizationOperation() ||
+ m.isInternal()))
+ {
+ throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+ ERR_MODIFY_ATTR_IS_OBSOLETE.get(
+ String.valueOf(entryDN), a.getName()));
+ }
+ }
+ }
+
+
+ // See if the attribute is one which controls the privileges available for
+ // a user. If it is, then the client must have the PRIVILEGE_CHANGE
+ // privilege.
+ if (t.hasName(OP_ATTR_PRIVILEGE_NAME))
+ {
+ if (! clientConnection.hasPrivilege(Privilege.PRIVILEGE_CHANGE, this))
+ {
+ throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
+ ERR_MODIFY_CHANGE_PRIVILEGE_INSUFFICIENT_PRIVILEGES.get());
+ }
+ }
+
+
+ // If the modification is updating the password attribute, then perform
+ // any necessary password policy processing. This processing should be
+ // skipped for synchronization operations.
+ boolean isPassword =
+ t.equals(pwPolicyState.getPolicy().getPasswordAttribute());
+ if (isPassword && (!(isSynchronizationOperation())))
+ {
+ // If the attribute contains any options, then reject it. Passwords
+ // will not be allowed to have options. Skipped for internal operations.
+ if(! isInternalOperation())
+ {
+ if (a.hasOptions())
+ {
+ throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+ ERR_MODIFY_PASSWORDS_CANNOT_HAVE_OPTIONS.get());
+ }
+
+
+ // If it's a self change, then see if that's allowed.
+ if (selfChange &&
+ (! pwPolicyState.getPolicy().allowUserPasswordChanges()))
+ {
+ pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
+ throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+ ERR_MODIFY_NO_USER_PW_CHANGES.get());
+ }
+
+
+ // If we require secure password changes, then makes sure it's a
+ // secure communication channel.
+ if (pwPolicyState.getPolicy().requireSecurePasswordChanges() &&
+ (! clientConnection.isSecure()))
+ {
+ pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
+ throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+ ERR_MODIFY_REQUIRE_SECURE_CHANGES.get());
+ }
+
+
+ // If it's a self change and it's not been long enough since the
+ // previous change, then reject it.
+ if (selfChange && pwPolicyState.isWithinMinimumAge())
+ {
+ pwpErrorType = PasswordPolicyErrorType.PASSWORD_TOO_YOUNG;
+ throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+ ERR_MODIFY_WITHIN_MINIMUM_AGE.get());
+ }
+ }
+
+ // Check to see whether this will adding, deleting, or replacing
+ // password values (increment doesn't make any sense for passwords).
+ // Then perform the appropriate type of processing for that kind of
+ // modification.
+ boolean isAdd = (m.getModificationType() == ModificationType.ADD);
+ LinkedHashSet<AttributeValue> pwValues = a.getValues();
+ LinkedHashSet<AttributeValue> encodedValues =
+ new LinkedHashSet<AttributeValue>();
+ switch (m.getModificationType())
+ {
+ case ADD:
+ case REPLACE:
+ processInitialAddOrReplacePW(isAdd, pwValues, encodedValues, a);
+ break;
+
+ case DELETE:
+ processInitialDeletePW(pwValues, encodedValues, a);
+ break;
+
+ default:
+ throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+ ERR_MODIFY_INVALID_MOD_TYPE_FOR_PASSWORD.get(
+ String.valueOf(m.getModificationType()),
+ a.getName()));
+ }
+ }
+ else
+ {
+ // See if it's an attribute used to maintain the account
+ // enabled/disabled state.
+ AttributeType disabledAttr =
+ DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_DISABLED, true);
+ if (t.equals(disabledAttr))
+ {
+ enabledStateChanged = true;
+ for (AttributeValue v : a.getValues())
+ {
+ try
+ {
+ isEnabled =
+ (! BooleanSyntax.decodeBooleanValue(v.getNormalizedValue()));
+ }
+ catch (DirectoryException de)
+ {
+ throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+ ERR_MODIFY_INVALID_DISABLED_VALUE.get(
+ OP_ATTR_ACCOUNT_DISABLED,
+ String.valueOf(de.getMessageObject())), de);
+ }
+ }
+ }
+ }
+
+
+ switch (m.getModificationType())
+ {
+ case ADD:
+ processInitialAddSchema(a);
+ break;
+
+ case DELETE:
+ processInitialDeleteSchema(a);
+ break;
+
+ case REPLACE:
+ processInitialReplaceSchema(a);
+ break;
+
+ case INCREMENT:
+ processInitialIncrementSchema(a);
+ break;
+ }
+ }
+ }
+
+
+
+ /**
+ * Performs the initial password policy add or replace processing.
+ *
+ * @param isAdd Indicates whether it is an add or replace update.
+ * @param pwValues The set of password values as included in the
+ * request.
+ * @param encodedValues The set of encoded password values.
+ * @param pwAttr The attribute involved in the password change.
+ *
+ * @throws DirectoryException If a problem occurs that should cause the
+ * modify operation to fail.
+ */
+ private void processInitialAddOrReplacePW(boolean isAdd,
+ LinkedHashSet<AttributeValue> pwValues,
+ LinkedHashSet<AttributeValue> encodedValues,
+ Attribute pwAttr)
+ throws DirectoryException
+ {
+ int passwordsToAdd = pwValues.size();
+
+ if (isAdd)
+ {
+ numPasswords += passwordsToAdd;
+ }
+ else
+ {
+ numPasswords = passwordsToAdd;
+ }
+
+
+ // If there were multiple password values, then make sure that's OK.
+ if ((! isInternalOperation()) &&
+ (! pwPolicyState.getPolicy().allowMultiplePasswordValues()) &&
+ (passwordsToAdd > 1))
+ {
+ pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
+ throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+ ERR_MODIFY_MULTIPLE_VALUES_NOT_ALLOWED.get());
+ }
+
+
+ // Iterate through the password values and see if any of them are
+ // pre-encoded. If so, then check to see if we'll allow it. Otherwise,
+ // store the clear-text values for later validation and update the attribute
+ // with the encoded values.
+ for (AttributeValue v : pwValues)
+ {
+ if (pwPolicyState.passwordIsPreEncoded(v.getValue()))
+ {
+ if ((! isInternalOperation()) &&
+ ! pwPolicyState.getPolicy().allowPreEncodedPasswords())
+ {
+ pwpErrorType = PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY;
+ throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+ ERR_MODIFY_NO_PREENCODED_PASSWORDS.get());
+ }
+ else
+ {
+ encodedValues.add(v);
+ }
+ }
+ else
+ {
+ if (isAdd)
+ {
+ // Make sure that the password value doesn't already exist.
+ if (pwPolicyState.passwordMatches(v.getValue()))
+ {
+ pwpErrorType = PasswordPolicyErrorType.PASSWORD_IN_HISTORY;
+ throw new DirectoryException(ResultCode.ATTRIBUTE_OR_VALUE_EXISTS,
+ ERR_MODIFY_PASSWORD_EXISTS.get());
+ }
+ }
+
+ if (newPasswords == null)
+ {
+ newPasswords = new LinkedList<AttributeValue>();
+ }
+
+ newPasswords.add(v);
+
+ for (ByteString s : pwPolicyState.encodePassword(v.getValue()))
+ {
+ encodedValues.add(new AttributeValue(pwAttr.getAttributeType(), s));
+ }
+ }
+ }
+
+ pwAttr.setValues(encodedValues);
+ }
+
+
+
+ /**
+ * Performs the initial password policy delete processing.
+ *
+ * @param pwValues The set of password values as included in the
+ * request.
+ * @param encodedValues The set of encoded password values.
+ * @param pwAttr The attribute involved in the password change.
+ *
+ * @throws DirectoryException If a problem occurs that should cause the
+ * modify operation to fail.
+ */
+ private void processInitialDeletePW(LinkedHashSet<AttributeValue> pwValues,
+ LinkedHashSet<AttributeValue> encodedValues,
+ Attribute pwAttr)
+ throws DirectoryException
+ {
+ // Iterate through the password values and see if any of them are
+ // pre-encoded. We will never allow pre-encoded passwords for user password
+ // changes, but we will allow them for administrators. For each clear-text
+ // value, verify that at least one value in the entry matches and replace
+ // the clear-text value with the appropriate encoded forms.
+ for (AttributeValue v : pwValues)
+ {
+ if (pwPolicyState.passwordIsPreEncoded(v.getValue()))
+ {
+ if ((! isInternalOperation()) && selfChange)
+ {
+ pwpErrorType = PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY;
+ throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+ ERR_MODIFY_NO_PREENCODED_PASSWORDS.get());
+ }
+ else
+ {
+ encodedValues.add(v);
+ }
+ }
+ else
+ {
+ List<Attribute> attrList =
+ currentEntry.getAttribute(pwAttr.getAttributeType());
+ if ((attrList == null) || (attrList.isEmpty()))
+ {
+ throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+ ERR_MODIFY_NO_EXISTING_VALUES.get());
+ }
+ boolean found = false;
+ for (Attribute attr : attrList)
+ {
+ for (AttributeValue av : attr.getValues())
+ {
+ if (pwPolicyState.getPolicy().usesAuthPasswordSyntax())
+ {
+ if (AuthPasswordSyntax.isEncoded(av.getValue()))
+ {
+ StringBuilder[] compoenents =
+ AuthPasswordSyntax.decodeAuthPassword(av.getStringValue());
+ PasswordStorageScheme scheme =
+ DirectoryServer.getAuthPasswordStorageScheme(
+ compoenents[0].toString());
+ if (scheme != null)
+ {
+ if (scheme.authPasswordMatches(v.getValue(),
+ compoenents[1].toString(),
+ compoenents[2].toString()))
+ {
+ encodedValues.add(av);
+ found = true;
+ }
+ }
+ }
+ else
+ {
+ if (av.equals(v))
+ {
+ encodedValues.add(v);
+ found = true;
+ }
+ }
+ }
+ else
+ {
+ if (UserPasswordSyntax.isEncoded(av.getValue()))
+ {
+ String[] compoenents = UserPasswordSyntax.decodeUserPassword(
+ av.getStringValue());
+ PasswordStorageScheme scheme =
+ DirectoryServer.getPasswordStorageScheme(
+ toLowerCase(compoenents[0]));
+ if (scheme != null)
+ {
+ if (scheme.passwordMatches(v.getValue(),
+ new ASN1OctetString(compoenents[1])))
+ {
+ encodedValues.add(av);
+ found = true;
+ }
+ }
+ }
+ else
+ {
+ if (av.equals(v))
+ {
+ encodedValues.add(v);
+ found = true;
+ }
+ }
+ }
+ }
+ }
+
+ if (found)
+ {
+ if (currentPasswords == null)
+ {
+ currentPasswords = new LinkedList<AttributeValue>();
+ }
+ currentPasswords.add(v);
+ numPasswords--;
+ }
+ else
+ {
+ throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+ ERR_MODIFY_INVALID_PASSWORD.get());
+ }
+
+ currentPasswordProvided = true;
+ }
+ }
+
+ pwAttr.setValues(encodedValues);
+ }
+
+
+
+ /**
+ * Performs the initial schema processing for an add modification.
+ *
+ * @param attr The attribute being added.
+ *
+ * @throws DirectoryException If a problem occurs that should cause the
+ * modify operation to fail.
+ */
+ private void processInitialAddSchema(Attribute attr)
+ throws DirectoryException
+ {
+ // Make sure that one or more values have been provided for the attribute.
+ LinkedHashSet<AttributeValue> newValues = attr.getValues();
+ if ((newValues == null) || newValues.isEmpty())
+ {
+ throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
+ ERR_MODIFY_ADD_NO_VALUES.get(String.valueOf(entryDN),
+ attr.getName()));
+ }
+
+ // If the server is configured to check schema and the operation is not a
+ // synchronization operation, make sure that all the new values are valid
+ // according to the associated syntax.
+ if ((DirectoryServer.checkSchema()) && (! isSynchronizationOperation()))
+ {
+ AcceptRejectWarn syntaxPolicy =
+ DirectoryServer.getSyntaxEnforcementPolicy();
+ AttributeSyntax syntax = attr.getAttributeType().getSyntax();
+
+ if (syntaxPolicy == AcceptRejectWarn.REJECT)
+ {
+ MessageBuilder invalidReason = new MessageBuilder();
+ for (AttributeValue v : newValues)
+ {
+ if (! syntax.valueIsAcceptable(v.getValue(), invalidReason))
+ {
+ throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+ ERR_MODIFY_ADD_INVALID_SYNTAX.get(
+ String.valueOf(entryDN), attr.getName(),
+ v.getStringValue(), invalidReason));
+ }
+ }
+ }
+ else if (syntaxPolicy == AcceptRejectWarn.WARN)
+ {
+ MessageBuilder invalidReason = new MessageBuilder();
+ for (AttributeValue v : newValues)
+ {
+ if (! syntax.valueIsAcceptable(v.getValue(), invalidReason))
+ {
+ setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
+ logError(ERR_MODIFY_ADD_INVALID_SYNTAX.get(String.valueOf(entryDN),
+ attr.getName(), v.getStringValue(), invalidReason));
+ invalidReason = new MessageBuilder();
+ }
+ }
+ }
+ }
+
+
+ // Add the provided attribute or merge an existing attribute with
+ // the values of the new attribute. If there are any duplicates,
+ // then fail.
+ if (attr.getAttributeType().isObjectClassType())
+ {
+ modifiedEntry.addObjectClasses(newValues);
+ }
+ else
+ {
+ LinkedList<AttributeValue> duplicateValues =
+ new LinkedList<AttributeValue>();
+ modifiedEntry.addAttribute(attr, duplicateValues);
+ if (! duplicateValues.isEmpty())
+ {
+ StringBuilder buffer = new StringBuilder();
+ Iterator<AttributeValue> iterator = duplicateValues.iterator();
+ buffer.append(iterator.next().getStringValue());
+ while (iterator.hasNext())
+ {
+ buffer.append(", ");
+ buffer.append(iterator.next().getStringValue());
+ }
+
+ throw new DirectoryException(ResultCode.ATTRIBUTE_OR_VALUE_EXISTS,
+ ERR_MODIFY_ADD_DUPLICATE_VALUE.get(
+ String.valueOf(entryDN), attr.getName(), buffer));
+ }
+ }
+ }
+
+
+
+ /**
+ * Performs the initial schema processing for a delete modification.
+ *
+ * @param attr The attribute being deleted.
+ *
+ * @throws DirectoryException If a problem occurs that should cause the
+ * modify operation to fail.
+ */
+ private void processInitialDeleteSchema(Attribute attr)
+ throws DirectoryException
+ {
+ // Remove the specified attribute values or the entire attribute from the
+ // value. If there are any specified values that were not present, then
+ // fail. If the RDN attribute value would be removed, then fail.
+ LinkedList<AttributeValue> missingValues = new LinkedList<AttributeValue>();
+ boolean attrExists = modifiedEntry.removeAttribute(attr, missingValues);
+
+ if (attrExists)
+ {
+ if (missingValues.isEmpty())
+ {
+ AttributeType t = attr.getAttributeType();
+
+ RDN rdn = modifiedEntry.getDN().getRDN();
+ if ((rdn != null) && rdn.hasAttributeType(t) &&
+ (! modifiedEntry.hasValue(t, attr.getOptions(),
+ rdn.getAttributeValue(t))))
+ {
+ throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_RDN,
+ ERR_MODIFY_DELETE_RDN_ATTR.get(
+ String.valueOf(entryDN),
+ attr.getName()));
+ }
+ }
+ else
+ {
+ StringBuilder buffer = new StringBuilder();
+ Iterator<AttributeValue> iterator = missingValues.iterator();
+ buffer.append(iterator.next().getStringValue());
+ while (iterator.hasNext())
+ {
+ buffer.append(", ");
+ buffer.append(iterator.next().getStringValue());
+ }
+
+ throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE,
+ ERR_MODIFY_DELETE_MISSING_VALUES.get(
+ String.valueOf(entryDN), attr.getName(), buffer));
+ }
+ }
+ else
+ {
+ throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE,
+ ERR_MODIFY_DELETE_NO_SUCH_ATTR.get(
+ String.valueOf(entryDN), attr.getName()));
+ }
+ }
+
+
+
+ /**
+ * Performs the initial schema processing for a replace modification.
+ *
+ * @param attr The attribute being replaced.
+ *
+ * @throws DirectoryException If a problem occurs that should cause the
+ * modify operation to fail.
+ */
+ private void processInitialReplaceSchema(Attribute attr)
+ throws DirectoryException
+ {
+ // If it is the objectclass attribute, then treat that separately.
+ if (attr.getAttributeType().isObjectClassType())
+ {
+ modifiedEntry.setObjectClasses(attr.getValues());
+ return;
+ }
+
+
+ // If the provided attribute does not have any values, then we will simply
+ // remove the attribute from the entry (if it exists).
+ AttributeType t = attr.getAttributeType();
+ if (! attr.hasValue())
+ {
+ modifiedEntry.removeAttribute(t, attr.getOptions());
+ RDN rdn = modifiedEntry.getDN().getRDN();
+ if ((rdn != null) && rdn.hasAttributeType(t) &&
+ (! modifiedEntry.hasValue(t, attr.getOptions(),
+ rdn.getAttributeValue(t))))
+ {
+ throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_RDN,
+ ERR_MODIFY_DELETE_RDN_ATTR.get(String.valueOf(entryDN),
+ attr.getName()));
+ }
+
+ return;
+ }
+
+ // If the server is configured to check schema and the operation is not a
+ // synchronization operation, make sure that all the new values are valid
+ // according to the associated syntax.
+ LinkedHashSet<AttributeValue> newValues = attr.getValues();
+ if ((DirectoryServer.checkSchema()) && (! isSynchronizationOperation()))
+ {
+ AcceptRejectWarn syntaxPolicy =
+ DirectoryServer.getSyntaxEnforcementPolicy();
+ AttributeSyntax syntax = t.getSyntax();
+
+ if (syntaxPolicy == AcceptRejectWarn.REJECT)
+ {
+ MessageBuilder invalidReason = new MessageBuilder();
+ for (AttributeValue v : newValues)
+ {
+ if (! syntax.valueIsAcceptable(v.getValue(), invalidReason))
+ {
+ throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+ ERR_MODIFY_REPLACE_INVALID_SYNTAX.get(
+ String.valueOf(entryDN), attr.getName(),
+ v.getStringValue(), invalidReason));
+ }
+ }
+ }
+ else if (syntaxPolicy == AcceptRejectWarn.WARN)
+ {
+ MessageBuilder invalidReason = new MessageBuilder();
+ for (AttributeValue v : newValues)
+ {
+ if (! syntax.valueIsAcceptable(v.getValue(), invalidReason))
+ {
+ setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
+ logError(ERR_MODIFY_REPLACE_INVALID_SYNTAX.get(
+ String.valueOf(entryDN), attr.getName(),
+ v.getStringValue(), invalidReason));
+ invalidReason = new MessageBuilder();
+ }
+ }
+ }
+ }
+
+
+ // If the provided attribute does not have any options, then we will simply
+ // use it in place of any existing attribute of the provided type (or add it
+ // if it doesn't exist).
+ if (! attr.hasOptions())
+ {
+ List<Attribute> attrList = new ArrayList<Attribute>(1);
+ attrList.add(attr);
+ modifiedEntry.putAttribute(t, attrList);
+
+ RDN rdn = modifiedEntry.getDN().getRDN();
+ if ((rdn != null) && rdn.hasAttributeType(t) &&
+ (! modifiedEntry.hasValue(t, attr.getOptions(),
+ rdn.getAttributeValue(t))))
+ {
+ throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_RDN,
+ ERR_MODIFY_DELETE_RDN_ATTR.get(
+ String.valueOf(entryDN),
+ attr.getName()));
+ }
+
+ return;
+ }
+
+
+ // See if there is an existing attribute of the provided type. If not, then
+ // we'll use the new one.
+ List<Attribute> attrList = modifiedEntry.getAttribute(t);
+ if ((attrList == null) || attrList.isEmpty())
+ {
+ attrList = new ArrayList<Attribute>(1);
+ attrList.add(attr);
+ modifiedEntry.putAttribute(t, attrList);
+
+ RDN rdn = modifiedEntry.getDN().getRDN();
+ if ((rdn != null) && rdn.hasAttributeType(t) &&
+ (! modifiedEntry.hasValue(t, attr.getOptions(),
+ rdn.getAttributeValue(t))))
+ {
+ throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_RDN,
+ ERR_MODIFY_DELETE_RDN_ATTR.get(
+ String.valueOf(entryDN),
+ attr.getName()));
+ }
+
+ return;
+ }
+
+
+ // There must be an existing occurrence of the provided attribute in the
+ // entry. If there is a version with exactly the set of options provided,
+ // then replace it. Otherwise, add a new one.
+ boolean found = false;
+ for (int i=0; i < attrList.size(); i++)
+ {
+ if (attrList.get(i).optionsEqual(attr.getOptions()))
+ {
+ attrList.set(i, attr);
+ found = true;
+ break;
+ }
+ }
+
+ if (! found)
+ {
+ attrList.add(attr);
+ }
+
+ RDN rdn = modifiedEntry.getDN().getRDN();
+ if ((rdn != null) && rdn.hasAttributeType(t) &&
+ (! modifiedEntry.hasValue(t, attr.getOptions(),
+ rdn.getAttributeValue(t))))
+ {
+ throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_RDN,
+ ERR_MODIFY_DELETE_RDN_ATTR.get(
+ String.valueOf(entryDN),
+ attr.getName()));
+ }
+ }
+
+
+
+ /**
+ * Performs the initial schema processing for an increment modification.
+ *
+ * @param attr The attribute being incremented.
+ *
+ * @throws DirectoryException If a problem occurs that should cause the
+ * modify operation to fail.
+ */
+ private void processInitialIncrementSchema(Attribute attr)
+ throws DirectoryException
+ {
+ // The specified attribute type must not be an RDN attribute.
+ AttributeType t = attr.getAttributeType();
+ RDN rdn = modifiedEntry.getDN().getRDN();
+ if ((rdn != null) && rdn.hasAttributeType(t))
+ {
+ throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_RDN,
+ ERR_MODIFY_INCREMENT_RDN.get(
+ String.valueOf(entryDN),
+ attr.getName()));
+ }
+
+
+ // The provided attribute must have a single value, and it must be an
+ // integer.
+ LinkedHashSet<AttributeValue> values = attr.getValues();
+ if ((values == null) || values.isEmpty())
+ {
+ throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
+ ERR_MODIFY_INCREMENT_REQUIRES_VALUE.get(
+ String.valueOf(entryDN),
+ attr.getName()));
+ }
+ else if (values.size() > 1)
+ {
+ throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
+ ERR_MODIFY_INCREMENT_REQUIRES_SINGLE_VALUE.get(
+ String.valueOf(entryDN), attr.getName()));
+ }
+
+ AttributeValue v = values.iterator().next();
+
+ long incrementValue;
+ try
+ {
+ incrementValue = Long.parseLong(v.getNormalizedStringValue());
+ }
+ catch (Exception e)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+
+ throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+ ERR_MODIFY_INCREMENT_PROVIDED_VALUE_NOT_INTEGER.get(
+ String.valueOf(entryDN), attr.getName(),
+ v.getStringValue()), e);
+ }
+
+
+ // Get the corresponding attribute from the entry and make sure that it has
+ // a single integer value.
+ List<Attribute> attrList = modifiedEntry.getAttribute(t, attr.getOptions());
+ if ((attrList == null) || attrList.isEmpty())
+ {
+ throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+ ERR_MODIFY_INCREMENT_REQUIRES_EXISTING_VALUE.get(
+ String.valueOf(entryDN), attr.getName()));
+ }
+
+ boolean updated = false;
+ for (Attribute a : attrList)
+ {
+ LinkedHashSet<AttributeValue> valueList = a.getValues();
+ if ((valueList == null) || valueList.isEmpty())
+ {
+ continue;
+ }
+
+ LinkedHashSet<AttributeValue> newValueList =
+ new LinkedHashSet<AttributeValue>(valueList.size());
+ for (AttributeValue existingValue : valueList)
+ {
+ long newIntValue;
+ try
+ {
+ long existingIntValue =
+ Long.parseLong(existingValue.getStringValue());
+ newIntValue = existingIntValue + incrementValue;
+ }
+ catch (Exception e)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+
+ throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+ ERR_MODIFY_INCREMENT_REQUIRES_INTEGER_VALUE.get(
+ String.valueOf(entryDN), a.getName(),
+ existingValue.getStringValue()), e);
+ }
+
+ ByteString newValue = new ASN1OctetString(String.valueOf(newIntValue));
+ newValueList.add(new AttributeValue(t, newValue));
+ }
+
+ a.setValues(newValueList);
+ updated = true;
+ }
+
+ if (! updated)
+ {
+ throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+ ERR_MODIFY_INCREMENT_REQUIRES_EXISTING_VALUE.get(
+ String.valueOf(entryDN), attr.getName()));
+ }
+ }
+
+
+
+ /**
+ * Performs additional preliminary processing that is required for a password
+ * change.
+ *
+ * @throws DirectoryException If a problem occurs that should cause the
+ * modify operation to fail.
+ */
+ public void performAdditionalPasswordChangedProcessing()
+ throws DirectoryException
+ {
+ // If it was a self change, then see if the current password was provided
+ // and handle accordingly.
+ if (selfChange &&
+ pwPolicyState.getPolicy().requireCurrentPassword() &&
+ (! currentPasswordProvided))
+ {
+ pwpErrorType = PasswordPolicyErrorType.MUST_SUPPLY_OLD_PASSWORD;
+
+ throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+ ERR_MODIFY_PW_CHANGE_REQUIRES_CURRENT_PW.get());
+ }
+
+
+ // If this change would result in multiple password values, then see if
+ // that's OK.
+ if ((numPasswords > 1) &&
+ (! pwPolicyState.getPolicy().allowMultiplePasswordValues()))
+ {
+ pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
+ throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+ ERR_MODIFY_MULTIPLE_PASSWORDS_NOT_ALLOWED.get());
+ }
+
+
+ // If any of the password values should be validated, then do so now.
+ if (selfChange ||
+ (! pwPolicyState.getPolicy().skipValidationForAdministrators()))
+ {
+ if (newPasswords != null)
+ {
+ HashSet<ByteString> clearPasswords = new HashSet<ByteString>();
+ clearPasswords.addAll(pwPolicyState.getClearPasswords());
+
+ if (currentPasswords != null)
+ {
+ if (clearPasswords.isEmpty())
+ {
+ for (AttributeValue v : currentPasswords)
+ {
+ clearPasswords.add(v.getValue());
+ }
+ }
+ else
+ {
+ // NOTE: We can't rely on the fact that Set doesn't allow
+ // duplicates because technically it's possible that the values
+ // aren't duplicates if they are ASN.1 elements with different types
+ // (like 0x04 for a standard universal octet string type versus 0x80
+ // for a simple password in a bind operation). So we have to
+ // manually check for duplicates.
+ for (AttributeValue v : currentPasswords)
+ {
+ ByteString pw = v.getValue();
+
+ boolean found = false;
+ for (ByteString s : clearPasswords)
+ {
+ if (Arrays.equals(s.value(), pw.value()))
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (! found)
+ {
+ clearPasswords.add(pw);
+ }
+ }
+ }
+ }
+
+ for (AttributeValue v : newPasswords)
+ {
+ MessageBuilder invalidReason = new MessageBuilder();
+ if (! pwPolicyState.passwordIsAcceptable(this, modifiedEntry,
+ v.getValue(), clearPasswords, invalidReason))
+ {
+ pwpErrorType =
+ PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY;
+ throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+ ERR_MODIFY_PW_VALIDATION_FAILED.get(
+ invalidReason));
+ }
+ }
+ }
+ }
+
+
+ // If we should check the password history, then do so now.
+ if (pwPolicyState.maintainHistory())
+ {
+ if (newPasswords != null)
+ {
+ for (AttributeValue v : newPasswords)
+ {
+ if (pwPolicyState.isPasswordInHistory(v.getValue()))
+ {
+ if (selfChange || (! pwPolicyState.getPolicy().
+ skipValidationForAdministrators()))
+ {
+ pwpErrorType = PasswordPolicyErrorType.PASSWORD_IN_HISTORY;
+ throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+ ERR_MODIFY_PW_IN_HISTORY.get());
+ }
+ }
+ }
+
+ pwPolicyState.updatePasswordHistory();
+ }
+ }
+
+
+ // See if the account was locked for any reason.
+ wasLocked = pwPolicyState.lockedDueToIdleInterval() ||
+ pwPolicyState.lockedDueToMaximumResetAge() ||
+ pwPolicyState.lockedDueToFailures();
+
+ // Update the password policy state attributes in the user's entry. If the
+ // modification fails, then these changes won't be applied.
+ pwPolicyState.setPasswordChangedTime();
+ pwPolicyState.clearFailureLockout();
+ pwPolicyState.clearGraceLoginTimes();
+ pwPolicyState.clearWarnedTime();
+
+ if (pwPolicyState.getPolicy().forceChangeOnAdd() ||
+ pwPolicyState.getPolicy().forceChangeOnReset())
+ {
+ if (selfChange)
+ {
+ pwPolicyState.setMustChangePassword(false);
+ }
+ else
+ {
+ if ((pwpErrorType == null) &&
+ pwPolicyState.getPolicy().forceChangeOnReset())
+ {
+ pwpErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET;
+ }
+
+ pwPolicyState.setMustChangePassword(
+ pwPolicyState.getPolicy().forceChangeOnReset());
+ }
+ }
+
+ if (pwPolicyState.getPolicy().getRequireChangeByTime() > 0)
+ {
+ pwPolicyState.setRequiredChangeTime();
+ }
+
+ modifications.addAll(pwPolicyState.getModifications());
+ modifiedEntry.applyModifications(pwPolicyState.getModifications());
+ }
+
+
+
+ /**
+ * Checks to ensure that both the Directory Server and the backend are
+ * writable.
+ *
+ * @throws DirectoryException If the modify operation should not be allowed
+ * as a result of the writability check.
+ */
+ private void checkWritability()
+ throws DirectoryException
+ {
+ // If it is not a private backend, then check to see if the server or
+ // backend is operating in read-only mode.
+ if (! backend.isPrivateBackend())
+ {
+ switch (DirectoryServer.getWritabilityMode())
+ {
+ case DISABLED:
+ throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+ ERR_MODIFY_SERVER_READONLY.get(
+ String.valueOf(entryDN)));
+
+ case INTERNAL_ONLY:
+ if (! (isInternalOperation() || isSynchronizationOperation()))
+ {
+ throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+ ERR_MODIFY_SERVER_READONLY.get(
+ String.valueOf(entryDN)));
+ }
+ }
+
+ switch (backend.getWritabilityMode())
+ {
+ case DISABLED:
+ throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+ ERR_MODIFY_BACKEND_READONLY.get(
+ String.valueOf(entryDN)));
+
+ case INTERNAL_ONLY:
+ if (! isInternalOperation() || isSynchronizationOperation())
+ {
+ throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+ ERR_MODIFY_BACKEND_READONLY.get(
+ String.valueOf(entryDN)));
+ }
+ }
+ }
+ }
+
+
+
+ /**
+ * Handles any account status notifications that may be needed as a result of
+ * modify processing.
+ */
+ private void handleAccountStatusNotifications()
+ {
+ if (passwordChanged)
+ {
+ if (selfChange)
+ {
+ AuthenticationInfo authInfo = clientConnection.getAuthenticationInfo();
+ if (authInfo.getAuthenticationDN().equals(modifiedEntry.getDN()))
+ {
+ clientConnection.setMustChangePassword(false);
+ }
+
+ Message message = INFO_MODIFY_PASSWORD_CHANGED.get();
+ pwPolicyState.generateAccountStatusNotification(
+ AccountStatusNotificationType.PASSWORD_CHANGED,
+ modifiedEntry, message,
+ AccountStatusNotification.createProperties(pwPolicyState, false, -1,
+ currentPasswords, newPasswords));
+ }
+ else
+ {
+ Message message = INFO_MODIFY_PASSWORD_RESET.get();
+ pwPolicyState.generateAccountStatusNotification(
+ AccountStatusNotificationType.PASSWORD_RESET, modifiedEntry,
+ message,
+ AccountStatusNotification.createProperties(pwPolicyState, false, -1,
+ currentPasswords, newPasswords));
+ }
+ }
+
+ if (enabledStateChanged)
+ {
+ if (isEnabled)
+ {
+ Message message = INFO_MODIFY_ACCOUNT_ENABLED.get();
+ pwPolicyState.generateAccountStatusNotification(
+ AccountStatusNotificationType.ACCOUNT_ENABLED,
+ modifiedEntry, message,
+ AccountStatusNotification.createProperties(pwPolicyState, false, -1,
+ null, null));
+ }
+ else
+ {
+ Message message = INFO_MODIFY_ACCOUNT_DISABLED.get();
+ pwPolicyState.generateAccountStatusNotification(
+ AccountStatusNotificationType.ACCOUNT_DISABLED,
+ modifiedEntry, message,
+ AccountStatusNotification.createProperties(pwPolicyState, false, -1,
+ null, null));
+ }
+ }
+
+ if (wasLocked)
+ {
+ Message message = INFO_MODIFY_ACCOUNT_UNLOCKED.get();
+ pwPolicyState.generateAccountStatusNotification(
+ AccountStatusNotificationType.ACCOUNT_UNLOCKED, modifiedEntry,
+ message,
+ AccountStatusNotification.createProperties(pwPolicyState, false, -1,
+ null, null));
+ }
+ }
+
+
+
+ /**
+ * Handles any processing that is required for the LDAP pre-read and/or
+ * post-read controls.
+ */
+ private void handleReadEntryProcessing()
+ {
+ if (preReadRequest != null)
+ {
+ Entry entry = currentEntry.duplicate(true);
+
+ if (! preReadRequest.allowsAttribute(
+ DirectoryServer.getObjectClassAttributeType()))
+ {
+ entry.removeAttribute(
+ DirectoryServer.getObjectClassAttributeType());
+ }
+
+ if (! preReadRequest.returnAllUserAttributes())
+ {
+ Iterator<AttributeType> iterator =
+ entry.getUserAttributes().keySet().iterator();
+ while (iterator.hasNext())
+ {
+ AttributeType attrType = iterator.next();
+ if (! preReadRequest.allowsAttribute(attrType))
+ {
+ iterator.remove();
+ }
+ }
+ }
+
+ if (! preReadRequest.returnAllOperationalAttributes())
+ {
+ Iterator<AttributeType> iterator =
+ entry.getOperationalAttributes().keySet().iterator();
+ while (iterator.hasNext())
+ {
+ AttributeType attrType = iterator.next();
+ if (! preReadRequest.allowsAttribute(attrType))
+ {
+ iterator.remove();
+ }
+ }
+ }
+
+ // FIXME -- Check access controls on the entry to see if it should be
+ // returned or if any attributes need to be stripped out..
+ SearchResultEntry searchEntry = new SearchResultEntry(entry);
+ LDAPPreReadResponseControl responseControl =
+ new LDAPPreReadResponseControl(preReadRequest.getOID(),
+ preReadRequest.isCritical(),
+ searchEntry);
+ getResponseControls().add(responseControl);
+ }
+
+ if (postReadRequest != null)
+ {
+ Entry entry = modifiedEntry.duplicate(true);
+
+ if (! postReadRequest.allowsAttribute(
+ DirectoryServer.getObjectClassAttributeType()))
+ {
+ entry.removeAttribute(
+ DirectoryServer.getObjectClassAttributeType());
+ }
+
+ if (! postReadRequest.returnAllUserAttributes())
+ {
+ Iterator<AttributeType> iterator =
+ entry.getUserAttributes().keySet().iterator();
+ while (iterator.hasNext())
+ {
+ AttributeType attrType = iterator.next();
+ if (! postReadRequest.allowsAttribute(attrType))
+ {
+ iterator.remove();
+ }
+ }
+ }
+
+ if (! postReadRequest.returnAllOperationalAttributes())
+ {
+ Iterator<AttributeType> iterator =
+ entry.getOperationalAttributes().keySet().iterator();
+ while (iterator.hasNext())
+ {
+ AttributeType attrType = iterator.next();
+ if (! postReadRequest.allowsAttribute(attrType))
+ {
+ iterator.remove();
+ }
+ }
+ }
+
+ // FIXME -- Check access controls on the entry to see if it should be
+ // returned or if any attributes need to be stripped out..
+ SearchResultEntry searchEntry = new SearchResultEntry(entry);
+ LDAPPostReadResponseControl responseControl =
+ new LDAPPostReadResponseControl(postReadRequest.getOID(),
+ postReadRequest.isCritical(),
+ searchEntry);
+
+ getResponseControls().add(responseControl);
+ }
+ }
}
+
--
Gitblit v1.10.0