/*
|
* CDDL HEADER START
|
*
|
* The contents of this file are subject to the terms of the
|
* Common Development and Distribution License, Version 1.0 only
|
* (the "License"). You may not use this file except in compliance
|
* with the License.
|
*
|
* You can obtain a copy of the license at
|
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
|
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
|
* See the License for the specific language governing permissions
|
* and limitations under the License.
|
*
|
* When distributing Covered Code, include this CDDL HEADER in each
|
* file and include the License file at
|
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
|
* add the following below this CDDL HEADER, with the fields enclosed
|
* by brackets "[]" replaced with your own identifying information:
|
* Portions Copyright [yyyy] [name of copyright owner]
|
*
|
* CDDL HEADER END
|
*
|
*
|
* Portions Copyright 2007 Sun Microsystems, Inc.
|
*/
|
package org.opends.server.workflowelement.localbackend;
|
|
|
|
import java.util.ArrayList;
|
import java.util.HashSet;
|
import java.util.Iterator;
|
import java.util.LinkedHashSet;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
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.PasswordValidator;
|
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.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.AddOperation;
|
import org.opends.server.core.AddOperationWrapper;
|
import org.opends.server.core.DirectoryServer;
|
import org.opends.server.core.PasswordPolicy;
|
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.Attribute;
|
import org.opends.server.types.AttributeType;
|
import org.opends.server.types.AttributeValue;
|
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.ObjectClass;
|
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.PostOperationAddOperation;
|
import org.opends.server.types.operation.PostResponseAddOperation;
|
import org.opends.server.types.operation.PreOperationAddOperation;
|
import org.opends.server.types.operation.PostSynchronizationAddOperation;
|
import org.opends.server.util.TimeThread;
|
|
import static org.opends.messages.CoreMessages.*;
|
import static org.opends.server.loggers.ErrorLogger.*;
|
import static org.opends.server.loggers.debug.DebugLogger.*;
|
import static org.opends.server.config.ConfigConstants.*;
|
import static org.opends.server.util.ServerConstants.*;
|
import static org.opends.server.util.StaticUtils.*;
|
|
|
|
/**
|
* This class defines an operation used to add an entry in a local backend
|
* of the Directory Server.
|
*/
|
public class LocalBackendAddOperation
|
extends AddOperationWrapper
|
implements PreOperationAddOperation, PostOperationAddOperation,
|
PostResponseAddOperation, PostSynchronizationAddOperation
|
{
|
/**
|
* The tracer object for the debug logger.
|
*/
|
private static final DebugTracer TRACER = getTracer();
|
|
|
|
// The backend in which the entry is to be added.
|
private Backend backend;
|
|
// Indicates whether the request includes the LDAP no-op control.
|
private boolean noOp;
|
|
// Indicates whether to skip post-operation plugin processing.
|
private boolean skipPostOperation;
|
|
// The DN of the entry to be added.
|
private DN entryDN;
|
|
// The entry being added to the server.
|
private Entry entry;
|
|
// The post-read request control included in the request, if applicable.
|
LDAPPostReadRequestControl postReadRequest;
|
|
// The set of object classes for the entry to add.
|
private Map<ObjectClass, String> objectClasses;
|
|
// The set of operational attributes for the entry to add.
|
private Map<AttributeType,List<Attribute>> operationalAttributes;
|
|
// The set of user attributes for the entry to add.
|
private Map<AttributeType,List<Attribute>> userAttributes;
|
|
|
|
/**
|
* Creates a new operation that may be used to add a new entry in a
|
* local backend of the Directory Server.
|
*
|
* @param add The operation to enhance.
|
*/
|
public LocalBackendAddOperation(AddOperation add)
|
{
|
super(add);
|
|
LocalBackendWorkflowElement.attachLocalOperation (add, this);
|
}
|
|
|
|
/**
|
* Retrieves the entry to be added to the server. Note that this will not be
|
* available to pre-parse plugins or during the conflict resolution portion of
|
* the synchronization processing.
|
*
|
* @return The entry to be added to the server, or <CODE>null</CODE> if it is
|
* not yet available.
|
*/
|
public final Entry getEntryToAdd()
|
{
|
return entry;
|
}
|
|
|
|
/**
|
* Process this add operation against a local backend.
|
*
|
* @param backend The backend in which the add operation should be
|
* processed.
|
*/
|
void processLocalAdd(Backend backend)
|
{
|
this.backend = backend;
|
ClientConnection clientConnection = getClientConnection();
|
|
// Get the plugin config manager that will be used for invoking plugins.
|
PluginConfigManager pluginConfigManager =
|
DirectoryServer.getPluginConfigManager();
|
skipPostOperation = false;
|
|
// Check for a request to cancel this operation.
|
if (cancelIfRequested())
|
{
|
return;
|
}
|
|
// Create a labeled block of code that we can break out of if a problem is
|
// detected.
|
addProcessing:
|
{
|
// Process the entry DN and set of attributes to convert them from their
|
// raw forms as provided by the client to the forms required for the rest
|
// of the add processing.
|
entryDN = getEntryDN();
|
if (entryDN == null)
|
{
|
break addProcessing;
|
}
|
|
objectClasses = getObjectClasses();
|
userAttributes = getUserAttributes();
|
operationalAttributes = getOperationalAttributes();
|
|
if ((objectClasses == null ) || (userAttributes == null) ||
|
(operationalAttributes == null))
|
{
|
break addProcessing;
|
}
|
|
// Check for a request to cancel this operation.
|
if (cancelIfRequested())
|
{
|
return;
|
}
|
|
|
// Grab a read lock on the parent entry, if there is one. We need to do
|
// this to ensure that the parent is not deleted or renamed while this add
|
// is in progress, and we could also need it to check the entry against
|
// a DIT structure rule.
|
Lock parentLock = null;
|
Lock entryLock = null;
|
|
DN parentDN = entryDN.getParentDNInSuffix();
|
try
|
{
|
parentLock = lockParent(parentDN);
|
}
|
catch (DirectoryException de)
|
{
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, de);
|
}
|
|
setResponseData(de);
|
break addProcessing;
|
}
|
|
try
|
{
|
// Check for a request to cancel this operation.
|
if (cancelIfRequested())
|
{
|
return;
|
}
|
|
|
// Grab a write lock on the target entry. We'll need to do this
|
// eventually anyway, and we want to make sure that the two locks are
|
// always released when exiting this method, no matter what. Since
|
// the entry shouldn't exist yet, locking earlier than necessary
|
// shouldn't cause a problem.
|
for (int i=0; i < 3; i++)
|
{
|
entryLock = LockManager.lockWrite(entryDN);
|
if (entryLock != null)
|
{
|
break;
|
}
|
}
|
|
if (entryLock == null)
|
{
|
setResultCode(DirectoryServer.getServerErrorResultCode());
|
appendErrorMessage(ERR_ADD_CANNOT_LOCK_ENTRY.get(
|
String.valueOf(entryDN)));
|
|
skipPostOperation = true;
|
break addProcessing;
|
}
|
|
|
// 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 addProcessing;
|
}
|
}
|
catch (DirectoryException de)
|
{
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, de);
|
}
|
|
logError(ERR_ADD_SYNCH_CONFLICT_RESOLUTION_FAILED.get(
|
getConnectionID(), getOperationID(),
|
getExceptionMessage(de)));
|
|
setResponseData(de);
|
break addProcessing;
|
}
|
}
|
|
for (AttributeType at : userAttributes.keySet())
|
{
|
// 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.
|
// This must be done before running the password policy code
|
// and any other code that may add attributes marked as
|
// "NO-USER-MODIFICATION"
|
//
|
// Note that doing this checks at this time
|
// of the processing does not make it possible for pre-parse plugins
|
// to add NO-USER-MODIFICATION attributes to the entry.
|
if (at.isNoUserModification())
|
{
|
if (! (isInternalOperation() || isSynchronizationOperation()))
|
{
|
setResultCode(ResultCode.UNWILLING_TO_PERFORM);
|
appendErrorMessage(ERR_ADD_ATTR_IS_NO_USER_MOD.get(
|
String.valueOf(entryDN),
|
at.getNameOrOID()));
|
|
break addProcessing;
|
}
|
}
|
}
|
|
for (AttributeType at : operationalAttributes.keySet())
|
{
|
if (at.isNoUserModification())
|
{
|
if (! (isInternalOperation() || isSynchronizationOperation()))
|
{
|
setResultCode(ResultCode.UNWILLING_TO_PERFORM);
|
appendErrorMessage(ERR_ADD_ATTR_IS_NO_USER_MOD.get(
|
String.valueOf(entryDN),
|
at.getNameOrOID()));
|
|
break addProcessing;
|
}
|
}
|
}
|
|
// Check to see if the entry already exists. We do this before
|
// checking whether the parent exists to ensure a referral entry
|
// above the parent results in a correct referral.
|
try
|
{
|
if (DirectoryServer.entryExists(entryDN))
|
{
|
setResultCode(ResultCode.ENTRY_ALREADY_EXISTS);
|
appendErrorMessage(ERR_ADD_ENTRY_ALREADY_EXISTS.get(
|
String.valueOf(entryDN)));
|
break addProcessing;
|
}
|
}
|
catch (DirectoryException de)
|
{
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, de);
|
}
|
|
setResponseData(de);
|
break addProcessing;
|
}
|
|
|
// Get the parent entry, if it exists.
|
Entry parentEntry = null;
|
if (parentDN != null)
|
{
|
try
|
{
|
parentEntry = DirectoryServer.getEntry(parentDN);
|
|
if (parentEntry == null)
|
{
|
DN matchedDN = parentDN.getParentDNInSuffix();
|
while (matchedDN != null)
|
{
|
try
|
{
|
if (DirectoryServer.entryExists(matchedDN))
|
{
|
setMatchedDN(matchedDN);
|
break;
|
}
|
}
|
catch (Exception e)
|
{
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, e);
|
}
|
break;
|
}
|
|
matchedDN = matchedDN.getParentDNInSuffix();
|
}
|
|
|
// The parent doesn't exist, so this add can't be successful.
|
setResultCode(ResultCode.NO_SUCH_OBJECT);
|
appendErrorMessage(ERR_ADD_NO_PARENT.get(String.valueOf(entryDN),
|
String.valueOf(parentDN)));
|
break addProcessing;
|
}
|
}
|
catch (DirectoryException de)
|
{
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, de);
|
}
|
|
setResponseData(de);
|
break addProcessing;
|
}
|
}
|
|
|
// Check to make sure that all of the RDN attributes are included as
|
// attribute values. If not, then either add them or report an error.
|
try
|
{
|
addRDNAttributesIfNecessary();
|
}
|
catch (DirectoryException de)
|
{
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, de);
|
}
|
|
setResponseData(de);
|
break addProcessing;
|
}
|
|
|
// Check to make sure that all objectclasses have their superior classes
|
// listed in the entry. If not, then add them.
|
HashSet<ObjectClass> additionalClasses = null;
|
for (ObjectClass oc : objectClasses.keySet())
|
{
|
ObjectClass superiorClass = oc.getSuperiorClass();
|
if ((superiorClass != null) &&
|
(! objectClasses.containsKey(superiorClass)))
|
{
|
if (additionalClasses == null)
|
{
|
additionalClasses = new HashSet<ObjectClass>();
|
}
|
|
additionalClasses.add(superiorClass);
|
}
|
}
|
|
if (additionalClasses != null)
|
{
|
for (ObjectClass oc : additionalClasses)
|
{
|
addObjectClassChain(oc);
|
}
|
}
|
|
|
// Create an entry object to encapsulate the set of attributes and
|
// objectclasses.
|
entry = new Entry(entryDN, objectClasses, userAttributes,
|
operationalAttributes);
|
|
// Check to see if the entry includes a privilege specification. If so,
|
// then the requester must have the PRIVILEGE_CHANGE privilege.
|
AttributeType privType =
|
DirectoryServer.getAttributeType(OP_ATTR_PRIVILEGE_NAME, true);
|
if (entry.hasAttribute(privType) &&
|
(! clientConnection.hasPrivilege(Privilege.PRIVILEGE_CHANGE, this)))
|
{
|
|
appendErrorMessage(
|
ERR_ADD_CHANGE_PRIVILEGE_INSUFFICIENT_PRIVILEGES.get());
|
setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
|
break addProcessing;
|
}
|
|
|
// If it's not a synchronization operation, then check
|
// to see if the entry contains one or more passwords and if they
|
// are valid in accordance with the password policies associated with
|
// the user. Also perform any encoding that might be required by
|
// password storage schemes.
|
if (! isSynchronizationOperation())
|
{
|
try
|
{
|
handlePasswordPolicy();
|
}
|
catch (DirectoryException de)
|
{
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, de);
|
}
|
|
setResponseData(de);
|
break addProcessing;
|
}
|
}
|
|
|
// If the server is configured to check schema and the
|
// operation is not a synchronization operation,
|
// check to see if the entry is valid according to the server schema,
|
// and also whether its attributes are valid according to their syntax.
|
if ((DirectoryServer.checkSchema()) && (! isSynchronizationOperation()))
|
{
|
try
|
{
|
checkSchema(parentEntry);
|
}
|
catch (DirectoryException de)
|
{
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, de);
|
}
|
|
setResponseData(de);
|
break addProcessing;
|
}
|
}
|
|
|
// Get the backend in which the add is to be performed.
|
if (backend == null)
|
{
|
setResultCode(ResultCode.NO_SUCH_OBJECT);
|
appendErrorMessage(Message.raw("No backend for entry " +
|
entryDN.toString())); // TODO: i18n
|
break addProcessing;
|
}
|
|
|
// Check to see if there are any controls in the request. If so, then
|
// see if there is any special processing required.
|
try
|
{
|
processControls(parentDN);
|
}
|
catch (DirectoryException de)
|
{
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, de);
|
}
|
|
setResponseData(de);
|
break addProcessing;
|
}
|
|
|
// Check to see if the client has permission to perform the add.
|
|
// FIXME: for now assume that this will check all permission
|
// pertinent to the operation. This includes proxy authorization
|
// and any other controls specified.
|
|
// FIXME: earlier checks to see if the entry already exists or
|
// if the parent entry does not exist may have already exposed
|
// sensitive information to the client.
|
if (AccessControlConfigManager.getInstance().getAccessControlHandler().
|
isAllowed(this) == false)
|
{
|
setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
|
appendErrorMessage(ERR_ADD_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
|
String.valueOf(entryDN)));
|
skipPostOperation = true;
|
break addProcessing;
|
}
|
|
// Check for a request to cancel this operation.
|
if (cancelIfRequested())
|
{
|
return;
|
}
|
|
|
// If the operation is not a synchronization operation,
|
// Invoke the pre-operation add plugins.
|
if (! isSynchronizationOperation())
|
{
|
PreOperationPluginResult preOpResult =
|
pluginConfigManager.invokePreOperationAddPlugins(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());
|
|
return;
|
}
|
else if (preOpResult.sendResponseImmediately())
|
{
|
skipPostOperation = true;
|
break addProcessing;
|
}
|
else if (preOpResult.skipCoreProcessing())
|
{
|
skipPostOperation = false;
|
break addProcessing;
|
}
|
}
|
|
|
// Check for a request to cancel this operation.
|
if (cancelIfRequested())
|
{
|
return;
|
}
|
|
|
// 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:
|
setResultCode(ResultCode.UNWILLING_TO_PERFORM);
|
appendErrorMessage(ERR_ADD_SERVER_READONLY.get(
|
String.valueOf(entryDN)));
|
break addProcessing;
|
|
case INTERNAL_ONLY:
|
if (! (isInternalOperation() || isSynchronizationOperation()))
|
{
|
setResultCode(ResultCode.UNWILLING_TO_PERFORM);
|
appendErrorMessage(ERR_ADD_SERVER_READONLY.get(
|
String.valueOf(entryDN)));
|
break addProcessing;
|
}
|
break;
|
}
|
|
switch (backend.getWritabilityMode())
|
{
|
case DISABLED:
|
setResultCode(ResultCode.UNWILLING_TO_PERFORM);
|
appendErrorMessage(ERR_ADD_BACKEND_READONLY.get(
|
String.valueOf(entryDN)));
|
break addProcessing;
|
|
case INTERNAL_ONLY:
|
if (! (isInternalOperation() || isSynchronizationOperation()))
|
{
|
setResultCode(ResultCode.UNWILLING_TO_PERFORM);
|
appendErrorMessage(ERR_ADD_BACKEND_READONLY.get(
|
String.valueOf(entryDN)));
|
break addProcessing;
|
}
|
break;
|
}
|
}
|
|
|
try
|
{
|
if (noOp)
|
{
|
appendErrorMessage(INFO_ADD_NOOP.get());
|
setResultCode(ResultCode.NO_OPERATION);
|
}
|
else
|
{
|
for (SynchronizationProvider provider :
|
DirectoryServer.getSynchronizationProviders())
|
{
|
try
|
{
|
SynchronizationProviderResult result =
|
provider.doPreOperation(this);
|
if (! result.continueOperationProcessing())
|
{
|
break addProcessing;
|
}
|
}
|
catch (DirectoryException de)
|
{
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, de);
|
}
|
|
logError(ERR_ADD_SYNCH_PREOP_FAILED.get(getConnectionID(),
|
getOperationID(), getExceptionMessage(de)));
|
setResponseData(de);
|
break addProcessing;
|
}
|
}
|
|
backend.addEntry(entry, this);
|
}
|
|
if (postReadRequest != null)
|
{
|
addPostReadResponse();
|
}
|
|
|
if (! noOp)
|
{
|
setResultCode(ResultCode.SUCCESS);
|
}
|
}
|
catch (DirectoryException de)
|
{
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, de);
|
}
|
|
setResponseData(de);
|
break addProcessing;
|
}
|
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 addProcessing;
|
}
|
}
|
finally
|
{
|
if (entryLock != null)
|
{
|
LockManager.unlock(entryDN, entryLock);
|
}
|
|
if (parentLock != null)
|
{
|
LockManager.unlock(parentDN, parentLock);
|
}
|
|
|
for (SynchronizationProvider provider :
|
DirectoryServer.getSynchronizationProviders())
|
{
|
try
|
{
|
provider.doPostOperation(this);
|
}
|
catch (DirectoryException de)
|
{
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, de);
|
}
|
|
logError(ERR_ADD_SYNCH_POSTOP_FAILED.get(getConnectionID(),
|
getOperationID(), getExceptionMessage(de)));
|
setResponseData(de);
|
break;
|
}
|
}
|
}
|
}
|
|
|
// Indicate that it is now too late to attempt to cancel the operation.
|
setCancelResult(CancelResult.TOO_LATE);
|
|
|
// Invoke the post-operation or post-synchronization add plugins.
|
if (isSynchronizationOperation())
|
{
|
if (getResultCode() == ResultCode.SUCCESS)
|
{
|
pluginConfigManager.invokePostSynchronizationAddPlugins(this);
|
}
|
}
|
else if (! skipPostOperation)
|
{
|
// FIXME -- Should this also be done while holding the locks?
|
PostOperationPluginResult postOpResult =
|
pluginConfigManager.invokePostOperationAddPlugins(this);
|
if (postOpResult.connectionTerminated())
|
{
|
// There's no point in continuing with anything. Log the result and
|
// return.
|
setResultCode(ResultCode.CANCELED);
|
appendErrorMessage(ERR_CANCELED_BY_POSTOP_DISCONNECT.get());
|
|
return;
|
}
|
}
|
|
|
// Notify any change notification listeners that might be registered with
|
// the server.
|
if ((getResultCode() == ResultCode.SUCCESS) && (entry != null))
|
{
|
for (ChangeNotificationListener changeListener :
|
DirectoryServer.getChangeNotificationListeners())
|
{
|
try
|
{
|
changeListener.handleAddOperation(this, entry);
|
}
|
catch (Exception e)
|
{
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, e);
|
}
|
|
logError(ERR_ADD_ERROR_NOTIFYING_CHANGE_LISTENER.get(
|
getExceptionMessage(e)));
|
}
|
}
|
}
|
}
|
|
|
|
/**
|
* Checks to determine whether there has been a request to cancel this
|
* operation. If so, then set the cancel result and processing stop time.
|
*
|
* @return {@code true} if there was a cancel request, or {@code false} if
|
* not.
|
*/
|
private boolean cancelIfRequested()
|
{
|
if (getCancelRequest() == null)
|
{
|
return false;
|
}
|
|
indicateCancelled(getCancelRequest());
|
setProcessingStopTime();
|
return true;
|
}
|
|
|
|
/**
|
* Acquire a read lock on the parent of the entry to add.
|
*
|
* @return The acquired read lock.
|
*
|
* @throws DirectoryException If a problem occurs while attempting to
|
* acquire the lock.
|
*/
|
private Lock lockParent(DN parentDN)
|
throws DirectoryException
|
{
|
Lock parentLock = null;
|
|
if (parentDN == null)
|
{
|
// Either this entry is a suffix or doesn't belong in the directory.
|
if (DirectoryServer.isNamingContext(entryDN))
|
{
|
// This is fine. This entry is one of the configured suffixes.
|
parentLock = null;
|
}
|
else if (entryDN.isNullDN())
|
{
|
// This is not fine. The root DSE cannot be added.
|
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
|
ERR_ADD_CANNOT_ADD_ROOT_DSE.get());
|
}
|
else
|
{
|
// The entry doesn't have a parent but isn't a suffix. This is not
|
// allowed.
|
throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
|
ERR_ADD_ENTRY_NOT_SUFFIX.get(
|
String.valueOf(entryDN)));
|
}
|
}
|
else
|
{
|
for (int i=0; i < 3; i++)
|
{
|
parentLock = LockManager.lockRead(parentDN);
|
if (parentLock != null)
|
{
|
break;
|
}
|
}
|
|
if (parentLock == null)
|
{
|
throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
|
ERR_ADD_CANNOT_LOCK_PARENT.get(
|
String.valueOf(entryDN),
|
String.valueOf(parentDN)));
|
}
|
}
|
|
return parentLock;
|
}
|
|
|
|
/**
|
* Adds any missing RDN attributes to the entry.
|
*
|
* @throws DirectoryException If the entry is missing one or more RDN
|
* attributes and the server is configured to
|
* reject such entries.
|
*/
|
private void addRDNAttributesIfNecessary()
|
throws DirectoryException
|
{
|
RDN rdn = entryDN.getRDN();
|
int numAVAs = rdn.getNumValues();
|
for (int i=0; i < numAVAs; i++)
|
{
|
AttributeType t = rdn.getAttributeType(i);
|
AttributeValue v = rdn.getAttributeValue(i);
|
String n = rdn.getAttributeName(i);
|
if (t.isOperational())
|
{
|
List<Attribute> attrList = operationalAttributes.get(t);
|
if (attrList == null)
|
{
|
if (isSynchronizationOperation() ||
|
DirectoryServer.addMissingRDNAttributes())
|
{
|
LinkedHashSet<AttributeValue> valueList =
|
new LinkedHashSet<AttributeValue>(1);
|
valueList.add(v);
|
|
attrList = new ArrayList<Attribute>();
|
attrList.add(new Attribute(t, n, valueList));
|
|
operationalAttributes.put(t, attrList);
|
}
|
else
|
{
|
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
|
ERR_ADD_MISSING_RDN_ATTRIBUTE.get(
|
String.valueOf(entryDN), n));
|
}
|
}
|
else
|
{
|
boolean found = false;
|
for (Attribute a : attrList)
|
{
|
if (a.hasOptions())
|
{
|
continue;
|
}
|
else
|
{
|
if (! a.hasValue(v))
|
{
|
a.getValues().add(v);
|
}
|
|
found = true;
|
break;
|
}
|
}
|
|
if (! found)
|
{
|
if (isSynchronizationOperation() ||
|
DirectoryServer.addMissingRDNAttributes())
|
{
|
LinkedHashSet<AttributeValue> valueList =
|
new LinkedHashSet<AttributeValue>(1);
|
valueList.add(v);
|
attrList.add(new Attribute(t, n, valueList));
|
}
|
else
|
{
|
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
|
ERR_ADD_MISSING_RDN_ATTRIBUTE.get(
|
String.valueOf(entryDN), n));
|
}
|
}
|
}
|
}
|
else
|
{
|
List<Attribute> attrList = userAttributes.get(t);
|
if (attrList == null)
|
{
|
if (isSynchronizationOperation() ||
|
DirectoryServer.addMissingRDNAttributes())
|
{
|
LinkedHashSet<AttributeValue> valueList =
|
new LinkedHashSet<AttributeValue>(1);
|
valueList.add(v);
|
|
attrList = new ArrayList<Attribute>();
|
attrList.add(new Attribute(t, n, valueList));
|
|
userAttributes.put(t, attrList);
|
}
|
else
|
{
|
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
|
ERR_ADD_MISSING_RDN_ATTRIBUTE.get(
|
String.valueOf(entryDN),n));
|
}
|
}
|
else
|
{
|
boolean found = false;
|
for (Attribute a : attrList)
|
{
|
if (a.hasOptions())
|
{
|
continue;
|
}
|
else
|
{
|
if (! a.hasValue(v))
|
{
|
a.getValues().add(v);
|
}
|
|
found = true;
|
break;
|
}
|
}
|
|
if (! found)
|
{
|
if (isSynchronizationOperation() ||
|
DirectoryServer.addMissingRDNAttributes())
|
{
|
LinkedHashSet<AttributeValue> valueList =
|
new LinkedHashSet<AttributeValue>(1);
|
valueList.add(v);
|
attrList.add(new Attribute(t, n, valueList));
|
}
|
else
|
{
|
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
|
ERR_ADD_MISSING_RDN_ATTRIBUTE.get(
|
String.valueOf(entryDN),n));
|
}
|
}
|
}
|
}
|
}
|
}
|
|
|
|
/**
|
* Adds the provided objectClass to the entry, along with its superior classes
|
* if appropriate.
|
*
|
* @param objectClass The objectclass to add to the entry.
|
*/
|
public final void addObjectClassChain(ObjectClass objectClass)
|
{
|
Map<ObjectClass, String> objectClasses = getObjectClasses();
|
if (objectClasses != null){
|
if (! objectClasses.containsKey(objectClass))
|
{
|
objectClasses.put(objectClass, objectClass.getNameOrOID());
|
}
|
|
ObjectClass superiorClass = objectClass.getSuperiorClass();
|
if ((superiorClass != null) &&
|
(! objectClasses.containsKey(superiorClass)))
|
{
|
addObjectClassChain(superiorClass);
|
}
|
}
|
}
|
|
|
|
/**
|
* Performs all password policy processing necessary for the provided add
|
* operation.
|
*
|
* @throws DirectoryException If a problem occurs while performing password
|
* policy processing for the add operation.
|
*/
|
public final void handlePasswordPolicy()
|
throws DirectoryException
|
{
|
// FIXME -- We need to check to see if the password policy subentry
|
// might be specified virtually rather than as a real
|
// attribute.
|
PasswordPolicy passwordPolicy = null;
|
List<Attribute> pwAttrList =
|
entry.getAttribute(OP_ATTR_PWPOLICY_POLICY_DN);
|
if ((pwAttrList != null) && (! pwAttrList.isEmpty()))
|
{
|
Attribute a = pwAttrList.get(0);
|
LinkedHashSet<AttributeValue> valueSet = a.getValues();
|
Iterator<AttributeValue> iterator = valueSet.iterator();
|
if (iterator.hasNext())
|
{
|
DN policyDN;
|
try
|
{
|
policyDN = DN.decode(iterator.next().getValue());
|
}
|
catch (DirectoryException de)
|
{
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, de);
|
}
|
|
throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
|
ERR_ADD_INVALID_PWPOLICY_DN_SYNTAX.get(
|
String.valueOf(entryDN),
|
de.getMessageObject()));
|
}
|
|
passwordPolicy = DirectoryServer.getPasswordPolicy(policyDN);
|
if (passwordPolicy == null)
|
{
|
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
|
ERR_ADD_NO_SUCH_PWPOLICY.get(
|
String.valueOf(entryDN),
|
String.valueOf(policyDN)));
|
}
|
}
|
}
|
|
if (passwordPolicy == null)
|
{
|
passwordPolicy = DirectoryServer.getDefaultPasswordPolicy();
|
}
|
|
// See if a password was specified.
|
AttributeType passwordAttribute = passwordPolicy.getPasswordAttribute();
|
List<Attribute> attrList = entry.getAttribute(passwordAttribute);
|
if ((attrList == null) || attrList.isEmpty())
|
{
|
// The entry doesn't have a password, so no action is required.
|
return;
|
}
|
else if (attrList.size() > 1)
|
{
|
// This must mean there are attribute options, which we won't allow for
|
// passwords.
|
Message message = ERR_PWPOLICY_ATTRIBUTE_OPTIONS_NOT_ALLOWED.get(
|
passwordAttribute.getNameOrOID());
|
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
|
}
|
|
Attribute passwordAttr = attrList.get(0);
|
if (passwordAttr.hasOptions())
|
{
|
Message message = ERR_PWPOLICY_ATTRIBUTE_OPTIONS_NOT_ALLOWED.get(
|
passwordAttribute.getNameOrOID());
|
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
|
}
|
|
LinkedHashSet<AttributeValue> values = passwordAttr.getValues();
|
if (values.isEmpty())
|
{
|
// This will be treated the same as not having a password.
|
return;
|
}
|
|
if ((! passwordPolicy.allowMultiplePasswordValues()) && (values.size() > 1))
|
{
|
// FIXME -- What if they're pre-encoded and might all be the same?
|
addPWPolicyControl(PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED);
|
|
Message message = ERR_PWPOLICY_MULTIPLE_PW_VALUES_NOT_ALLOWED.get(
|
passwordAttribute.getNameOrOID());
|
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
|
}
|
|
CopyOnWriteArrayList<PasswordStorageScheme> defaultStorageSchemes =
|
passwordPolicy.getDefaultStorageSchemes();
|
LinkedHashSet<AttributeValue> newValues =
|
new LinkedHashSet<AttributeValue>(defaultStorageSchemes.size());
|
for (AttributeValue v : values)
|
{
|
ByteString value = v.getValue();
|
|
// See if the password is pre-encoded.
|
if (passwordPolicy.usesAuthPasswordSyntax())
|
{
|
if (AuthPasswordSyntax.isEncoded(value))
|
{
|
if (passwordPolicy.allowPreEncodedPasswords())
|
{
|
newValues.add(v);
|
continue;
|
}
|
else
|
{
|
addPWPolicyControl(
|
PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY);
|
|
Message message = ERR_PWPOLICY_PREENCODED_NOT_ALLOWED.get(
|
passwordAttribute.getNameOrOID());
|
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
|
message);
|
}
|
}
|
}
|
else
|
{
|
if (UserPasswordSyntax.isEncoded(value))
|
{
|
if (passwordPolicy.allowPreEncodedPasswords())
|
{
|
newValues.add(v);
|
continue;
|
}
|
else
|
{
|
addPWPolicyControl(
|
PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY);
|
|
Message message = ERR_PWPOLICY_PREENCODED_NOT_ALLOWED.get(
|
passwordAttribute.getNameOrOID());
|
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
|
message);
|
}
|
}
|
}
|
|
|
// See if the password passes validation. We should only do this if
|
// validation should be performed for administrators.
|
if (! passwordPolicy.skipValidationForAdministrators())
|
{
|
// There are never any current passwords for an add operation.
|
HashSet<ByteString> currentPasswords = new HashSet<ByteString>(0);
|
MessageBuilder invalidReason = new MessageBuilder();
|
for (PasswordValidator<?> validator :
|
passwordPolicy.getPasswordValidators().values())
|
{
|
if (! validator.passwordIsAcceptable(value, currentPasswords, this,
|
entry, invalidReason))
|
{
|
addPWPolicyControl(
|
PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY);
|
|
Message message = ERR_PWPOLICY_VALIDATION_FAILED.
|
get(passwordAttribute.getNameOrOID(),
|
String.valueOf(invalidReason));
|
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
|
message);
|
}
|
}
|
}
|
|
|
// Encode the password.
|
if (passwordPolicy.usesAuthPasswordSyntax())
|
{
|
for (PasswordStorageScheme s : defaultStorageSchemes)
|
{
|
ByteString encodedValue = s.encodeAuthPassword(value);
|
newValues.add(new AttributeValue(passwordAttribute, encodedValue));
|
}
|
}
|
else
|
{
|
for (PasswordStorageScheme s : defaultStorageSchemes)
|
{
|
ByteString encodedValue = s.encodePasswordWithScheme(value);
|
newValues.add(new AttributeValue(passwordAttribute, encodedValue));
|
}
|
}
|
}
|
|
|
// Put the new encoded values in the entry.
|
passwordAttr.setValues(newValues);
|
|
|
// Set the password changed time attribute.
|
ByteString timeString =
|
new ASN1OctetString(TimeThread.getGeneralizedTime());
|
AttributeType changedTimeType =
|
DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_CHANGED_TIME_LC);
|
if (changedTimeType == null)
|
{
|
changedTimeType = DirectoryServer.getDefaultAttributeType(
|
OP_ATTR_PWPOLICY_CHANGED_TIME);
|
}
|
|
LinkedHashSet<AttributeValue> changedTimeValues =
|
new LinkedHashSet<AttributeValue>(1);
|
changedTimeValues.add(new AttributeValue(changedTimeType, timeString));
|
|
ArrayList<Attribute> changedTimeList = new ArrayList<Attribute>(1);
|
changedTimeList.add(new Attribute(changedTimeType,
|
OP_ATTR_PWPOLICY_CHANGED_TIME,
|
changedTimeValues));
|
|
entry.putAttribute(changedTimeType, changedTimeList);
|
|
|
// If we should force change on add, then set the appropriate flag.
|
if (passwordPolicy.forceChangeOnAdd())
|
{
|
addPWPolicyControl(PasswordPolicyErrorType.CHANGE_AFTER_RESET);
|
|
AttributeType resetType =
|
DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_RESET_REQUIRED_LC);
|
if (resetType == null)
|
{
|
resetType = DirectoryServer.getDefaultAttributeType(
|
OP_ATTR_PWPOLICY_RESET_REQUIRED);
|
}
|
|
LinkedHashSet<AttributeValue> resetValues = new
|
LinkedHashSet<AttributeValue>(1);
|
resetValues.add(BooleanSyntax.createBooleanValue(true));
|
|
ArrayList<Attribute> resetList = new ArrayList<Attribute>(1);
|
resetList.add(new Attribute(resetType, OP_ATTR_PWPOLICY_RESET_REQUIRED,
|
resetValues));
|
entry.putAttribute(resetType, resetList);
|
}
|
}
|
|
|
|
/**
|
* Adds a password policy response control if the corresponding request
|
* control was included.
|
*
|
* @param errorType The error type to use for the response control.
|
*/
|
private void addPWPolicyControl(PasswordPolicyErrorType errorType)
|
{
|
for (Control c : getRequestControls())
|
{
|
if (c.getOID().equals(OID_PASSWORD_POLICY_CONTROL))
|
{
|
addResponseControl(new PasswordPolicyResponseControl(null, 0,
|
errorType));
|
}
|
}
|
}
|
|
|
|
/**
|
* Verifies that the entry to be added conforms to the server schema.
|
*
|
* @param parentEntry The parent of the entry to add.
|
*
|
* @throws DirectoryException If the entry violates the server schema
|
* configuration.
|
*/
|
private void checkSchema(Entry parentEntry)
|
throws DirectoryException
|
{
|
MessageBuilder invalidReason = new MessageBuilder();
|
if (! entry.conformsToSchema(parentEntry, true, true, true, invalidReason))
|
{
|
throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION,
|
invalidReason.toMessage());
|
}
|
else
|
{
|
switch (DirectoryServer.getSyntaxEnforcementPolicy())
|
{
|
case REJECT:
|
invalidReason = new MessageBuilder();
|
for (List<Attribute> attrList : userAttributes.values())
|
{
|
for (Attribute a : attrList)
|
{
|
AttributeSyntax syntax = a.getAttributeType().getSyntax();
|
if (syntax != null)
|
{
|
for (AttributeValue v : a.getValues())
|
{
|
if (! syntax.valueIsAcceptable(v.getValue(), invalidReason))
|
{
|
Message message = WARN_ADD_OP_INVALID_SYNTAX.get(
|
String.valueOf(entryDN),
|
String.valueOf(v.getStringValue()),
|
String.valueOf(a.getName()),
|
String.valueOf(invalidReason));
|
|
throw new DirectoryException(
|
ResultCode.INVALID_ATTRIBUTE_SYNTAX,
|
message);
|
}
|
}
|
}
|
}
|
}
|
|
for (List<Attribute> attrList :
|
operationalAttributes.values())
|
{
|
for (Attribute a : attrList)
|
{
|
AttributeSyntax syntax = a.getAttributeType().getSyntax();
|
if (syntax != null)
|
{
|
for (AttributeValue v : a.getValues())
|
{
|
if (! syntax.valueIsAcceptable(v.getValue(),
|
invalidReason))
|
{
|
Message message = WARN_ADD_OP_INVALID_SYNTAX.
|
get(String.valueOf(entryDN),
|
String.valueOf(v.getStringValue()),
|
String.valueOf(a.getName()),
|
String.valueOf(invalidReason));
|
|
throw new DirectoryException(
|
ResultCode.INVALID_ATTRIBUTE_SYNTAX,
|
message);
|
}
|
}
|
}
|
}
|
}
|
|
break;
|
|
|
case WARN:
|
invalidReason = new MessageBuilder();
|
for (List<Attribute> attrList : userAttributes.values())
|
{
|
for (Attribute a : attrList)
|
{
|
AttributeSyntax syntax = a.getAttributeType().getSyntax();
|
if (syntax != null)
|
{
|
for (AttributeValue v : a.getValues())
|
{
|
if (! syntax.valueIsAcceptable(v.getValue(),
|
invalidReason))
|
{
|
logError(WARN_ADD_OP_INVALID_SYNTAX.get(
|
String.valueOf(entryDN),
|
String.valueOf(v.getStringValue()),
|
String.valueOf(a.getName()),
|
String.valueOf(invalidReason)));
|
}
|
}
|
}
|
}
|
}
|
|
for (List<Attribute> attrList : operationalAttributes.values())
|
{
|
for (Attribute a : attrList)
|
{
|
AttributeSyntax syntax = a.getAttributeType().getSyntax();
|
if (syntax != null)
|
{
|
for (AttributeValue v : a.getValues())
|
{
|
if (! syntax.valueIsAcceptable(v.getValue(),
|
invalidReason))
|
{
|
logError(WARN_ADD_OP_INVALID_SYNTAX.get(
|
String.valueOf(entryDN),
|
String.valueOf(v.getStringValue()),
|
String.valueOf(a.getName()),
|
String.valueOf(invalidReason)));
|
}
|
}
|
}
|
}
|
}
|
|
break;
|
}
|
}
|
|
|
// See if the entry contains any attributes or object classes marked
|
// OBSOLETE. If so, then reject the entry.
|
for (AttributeType at : userAttributes.keySet())
|
{
|
if (at.isObsolete())
|
{
|
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
|
WARN_ADD_ATTR_IS_OBSOLETE.get(
|
String.valueOf(entryDN),
|
at.getNameOrOID()));
|
}
|
}
|
|
for (AttributeType at : operationalAttributes.keySet())
|
{
|
if (at.isObsolete())
|
{
|
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
|
WARN_ADD_ATTR_IS_OBSOLETE.get(
|
String.valueOf(entryDN),
|
at.getNameOrOID()));
|
}
|
}
|
|
for (ObjectClass oc : objectClasses.keySet())
|
{
|
if (oc.isObsolete())
|
{
|
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
|
WARN_ADD_OC_IS_OBSOLETE.get(
|
String.valueOf(entryDN),
|
oc.getNameOrOID()));
|
}
|
}
|
}
|
|
|
|
/**
|
* Processes the set of controls contained in the add request.
|
*
|
* @param parentDN The DN of the parent of the entry to add.
|
*
|
* @throws DirectoryException If there is a problem with any of the
|
* request controls.
|
*/
|
private void processControls(DN parentDN)
|
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(parentDN, 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(entry))
|
{
|
throw new DirectoryException(ResultCode.ASSERTION_FAILED,
|
ERR_ADD_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_ADD_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_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 (! getClientConnection().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 (! getClientConnection().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))
|
{
|
// We don't need to do anything here because it's already handled
|
// in LocalBackendAddOperation.handlePasswordPolicy().
|
}
|
|
// NYI -- Add support for additional controls.
|
else if (c.isCritical())
|
{
|
if ((backend == null) || (! backend.supportsControl(oid)))
|
{
|
throw new DirectoryException(
|
ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
|
ERR_ADD_UNSUPPORTED_CRITICAL_CONTROL.get(
|
String.valueOf(entryDN), oid));
|
}
|
}
|
}
|
}
|
}
|
|
|
|
/**
|
* Adds the post-read response control to the response.
|
*/
|
private void addPostReadResponse()
|
{
|
Entry addedEntry = entry.duplicate(true);
|
|
if (! postReadRequest.allowsAttribute(
|
DirectoryServer.getObjectClassAttributeType()))
|
{
|
addedEntry.removeAttribute(DirectoryServer.getObjectClassAttributeType());
|
}
|
|
if (! postReadRequest.returnAllUserAttributes())
|
{
|
Iterator<AttributeType> iterator =
|
addedEntry.getUserAttributes().keySet().iterator();
|
while (iterator.hasNext())
|
{
|
AttributeType attrType = iterator.next();
|
if (! postReadRequest.allowsAttribute(attrType))
|
{
|
iterator.remove();
|
}
|
}
|
}
|
|
if (! postReadRequest.returnAllOperationalAttributes())
|
{
|
Iterator<AttributeType> iterator =
|
addedEntry.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(addedEntry);
|
LDAPPostReadResponseControl responseControl =
|
new LDAPPostReadResponseControl(postReadRequest.getOID(),
|
postReadRequest.isCritical(),
|
searchEntry);
|
addResponseControl(responseControl);
|
}
|
}
|