From 4ad090cb3d463eb6d5807e779505f89a15b420f6 Mon Sep 17 00:00:00 2001
From: jarnou <jarnou@localhost>
Date: Tue, 03 Jul 2007 10:06:12 +0000
Subject: [PATCH] Commits the refactoring of the core server to support proxy/distribution/virtual functionnalities This includes the new set of local operations as well as the workflows and the networkgroups.
---
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java | 203 +
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendSearchOperation.java | 111
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java | 6638 +++++++++++++++++++++++++++++++++++++++++++++++++++++
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendBindOperation.java | 57
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java | 82
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java | 335 ++
6 files changed, 7,426 insertions(+), 0 deletions(-)
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
new file mode 100644
index 0000000..681aff6
--- /dev/null
+++ b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
@@ -0,0 +1,335 @@
+/*
+ * 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 static org.opends.server.config.ConfigConstants.*;
+import static org.opends.server.messages.CoreMessages.*;
+import static org.opends.server.messages.MessageHandler.getMessage;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.opends.server.api.PasswordStorageScheme;
+import org.opends.server.api.PasswordValidator;
+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.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.DirectoryException;
+import org.opends.server.types.Entry;
+import org.opends.server.types.ObjectClass;
+import org.opends.server.types.ResultCode;
+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.util.TimeThread;
+
+/**
+ * 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
+{
+
+ // The entry being added to the server.
+ private Entry entry;
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Sets the entry to be added to the server.
+ *
+ * @param entry - The entry to be added to the server, or <CODE>null</CODE>
+ * if it is not yet available.
+ */
+ public final void setEntryToAdd(Entry entry){
+ this.entry = entry;
+ }
+
+ /**
+ * Performs all password policy processing necessary for the provided add
+ * operation.
+ *
+ * @param passwordPolicy The password policy associated with the entry to be
+ * added.
+ * @param userEntry The user entry being added.
+ *
+ * @throws DirectoryException If a problem occurs while performing password
+ * policy processing for the add operation.
+ */
+ public final void handlePasswordPolicy(PasswordPolicy passwordPolicy,
+ Entry userEntry)
+ throws DirectoryException
+ {
+ // See if a password was specified.
+ AttributeType passwordAttribute = passwordPolicy.getPasswordAttribute();
+ List<Attribute> attrList = userEntry.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.
+ int msgID = MSGID_PWPOLICY_ATTRIBUTE_OPTIONS_NOT_ALLOWED;
+ String message = getMessage(msgID, passwordAttribute.getNameOrOID());
+ throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message,
+ msgID);
+ }
+
+ Attribute passwordAttr = attrList.get(0);
+ if (passwordAttr.hasOptions())
+ {
+ int msgID = MSGID_PWPOLICY_ATTRIBUTE_OPTIONS_NOT_ALLOWED;
+ String message = getMessage(msgID, passwordAttribute.getNameOrOID());
+ throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message,
+ msgID);
+ }
+
+ 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?
+ int msgID = MSGID_PWPOLICY_MULTIPLE_PW_VALUES_NOT_ALLOWED;
+ String message = getMessage(msgID, passwordAttribute.getNameOrOID());
+ throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message,
+ msgID);
+ }
+
+ 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
+ {
+ int msgID = MSGID_PWPOLICY_PREENCODED_NOT_ALLOWED;
+ String message = getMessage(msgID,
+ passwordAttribute.getNameOrOID());
+ throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+ message, msgID);
+ }
+ }
+ }
+ else
+ {
+ if (UserPasswordSyntax.isEncoded(value))
+ {
+ if (passwordPolicy.allowPreEncodedPasswords())
+ {
+ newValues.add(v);
+ continue;
+ }
+ else
+ {
+ int msgID = MSGID_PWPOLICY_PREENCODED_NOT_ALLOWED;
+ String message = getMessage(msgID,
+ passwordAttribute.getNameOrOID());
+ throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+ message, msgID);
+ }
+ }
+ }
+
+
+ // 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);
+ StringBuilder invalidReason = new StringBuilder();
+ for (PasswordValidator<?> validator :
+ passwordPolicy.getPasswordValidators().values())
+ {
+ if (! validator.passwordIsAcceptable(value, currentPasswords, this,
+ userEntry, invalidReason))
+ {
+ int msgID = MSGID_PWPOLICY_VALIDATION_FAILED;
+ String message = getMessage(msgID, passwordAttribute.getNameOrOID(),
+ String.valueOf(invalidReason));
+ throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+ message, msgID);
+ }
+ }
+ }
+
+
+ // 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));
+
+ userEntry.putAttribute(changedTimeType, changedTimeList);
+
+
+ // If we should force change on add, then set the appropriate flag.
+ if (passwordPolicy.forceChangeOnAdd())
+ {
+ 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));
+ userEntry.putAttribute(resetType, resetList);
+ }
+ }
+
+ /**
+ * 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);
+ }
+ }
+ }
+
+}
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendBindOperation.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendBindOperation.java
new file mode 100644
index 0000000..e101a13
--- /dev/null
+++ b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendBindOperation.java
@@ -0,0 +1,57 @@
+/*
+ * 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 org.opends.server.core.BindOperation;
+import org.opends.server.core.BindOperationWrapper;
+import org.opends.server.types.operation.PostOperationBindOperation;
+import org.opends.server.types.operation.PostResponseBindOperation;
+import org.opends.server.types.operation.PreOperationBindOperation;
+
+/**
+ * This class defines an operation used to bind against the Directory Server,
+ * with the bound user entry within a local backend.
+ */
+public class LocalBackendBindOperation extends BindOperationWrapper
+ implements PreOperationBindOperation,
+ PostOperationBindOperation,
+ PostResponseBindOperation
+{
+
+ /**
+ * Creates a new operation that may be used to bind where
+ * the bound user entry is stored in a local backend of the Directory Server.
+ *
+ * @param bind The operation to enhance.
+ */
+ public LocalBackendBindOperation(BindOperation bind)
+ {
+ super(bind);
+ LocalBackendWorkflowElement.attachLocalOperation (bind, this);
+ }
+
+}
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java
new file mode 100644
index 0000000..b2d68ad
--- /dev/null
+++ b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java
@@ -0,0 +1,82 @@
+/*
+ * 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 org.opends.server.core.DeleteOperationWrapper;
+import org.opends.server.core.DeleteOperation;
+import org.opends.server.types.Entry;
+import org.opends.server.types.operation.PostOperationDeleteOperation;
+import org.opends.server.types.operation.PostResponseDeleteOperation;
+import org.opends.server.types.operation.PreOperationDeleteOperation;
+
+/**
+ * This class defines an operation used to delete an entry in a local backend
+ * of the Directory Server.
+ */
+public class LocalBackendDeleteOperation extends DeleteOperationWrapper
+ implements PreOperationDeleteOperation,
+ PostOperationDeleteOperation,
+ PostResponseDeleteOperation
+{
+ // The entry to be deleted.
+ private Entry entry;
+
+ /**
+ * Creates a new operation that may be used to delete an entry from a
+ * local backend of the Directory Server.
+ *
+ * @param delete The operation to enhance.
+ */
+ public LocalBackendDeleteOperation(DeleteOperation delete)
+ {
+ super(delete);
+ LocalBackendWorkflowElement.attachLocalOperation (delete, this);
+ }
+
+
+ /**
+ * Retrieves the entry to be deleted.
+ *
+ * @return The entry to be deleted, or <CODE>null</CODE> if the entry is not
+ * yet available.
+ */
+ public Entry getEntryToDelete()
+ {
+ return entry;
+ }
+
+ /**
+ * Sets the entry to be deleted.
+ *
+ * @param entry - The entry to be deleted
+ */
+ public void setEntryToDelete(Entry entry){
+ this.entry = entry;
+ }
+
+}
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
new file mode 100644
index 0000000..28449d3
--- /dev/null
+++ b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
@@ -0,0 +1,203 @@
+/*
+ * 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.List;
+
+import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.ModifyOperationWrapper;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.Entry;
+import org.opends.server.types.Modification;
+import org.opends.server.types.operation.PostOperationModifyOperation;
+import org.opends.server.types.operation.PostResponseModifyOperation;
+import org.opends.server.types.operation.PreOperationModifyOperation;
+
+/**
+ * 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
+{
+ // 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 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;
+
+ /**
+ * Creates a new operation that may be used to modify an entry in a
+ * local backend of the Directory Server.
+ *
+ * @param modify The operation to enhance.
+ */
+ public LocalBackendModifyOperation(ModifyOperation modify)
+ {
+ super(modify);
+ LocalBackendWorkflowElement.attachLocalOperation (modify, this);
+ }
+
+ /**
+ * Retrieves the current entry before any modifications are applied. This
+ * will not be available to pre-parse plugins.
+ *
+ * @return The current entry, or <CODE>null</CODE> if it is not yet
+ * available.
+ */
+ public final Entry getCurrentEntry()
+ {
+ 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
+ * one or more delete elements that target the password attribute and provide
+ * the values to delete in the clear. It will not be available to pre-parse
+ * plugins.
+ *
+ * @return The set of clear-text current password values as provided in the
+ * modify request, or <CODE>null</CODE> if there were none or this
+ * information is not yet available.
+ */
+ public final List<AttributeValue> getCurrentPasswords()
+ {
+ 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
+ * a change to this entry, then it is also necessary to add that change to
+ * the set of modifications to ensure that the update will be consistent.
+ *
+ * @return The modified entry that is to be written to the backend, or
+ * <CODE>null</CODE> if it is not yet available.
+ */
+ public final Entry getModifiedEntry()
+ {
+ 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
+ * add or replace elements that target the password attribute and provide the
+ * values in the clear. It will not be available to pre-parse plugins.
+ *
+ * @return 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 List<AttributeValue> getNewPasswords()
+ {
+ 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;
+ }
+
+
+ /**
+ * Adds the provided modification to the set of modifications to this modify
+ * operation.
+ * In addition, the modification is applied to the modified entry.
+ *
+ * This may only be called by pre-operation plugins.
+ *
+ * @param modification The modification to add to the set of changes for
+ * this modify operation.
+ *
+ * @throws DirectoryException If an unexpected problem occurs while applying
+ * the modification to the entry.
+ */
+ public void addModification(Modification modification)
+ throws DirectoryException
+ {
+ modifiedEntry.applyModification(modification);
+ super.addModification(modification);
+ }
+}
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendSearchOperation.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendSearchOperation.java
new file mode 100644
index 0000000..c13fe27
--- /dev/null
+++ b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendSearchOperation.java
@@ -0,0 +1,111 @@
+/*
+ * 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 org.opends.server.api.Backend;
+import org.opends.server.core.SearchOperationWrapper;
+import org.opends.server.core.SearchOperation;
+import org.opends.server.types.CancelResult;
+import org.opends.server.types.CancelledOperationException;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.operation.PostOperationSearchOperation;
+import org.opends.server.types.operation.PreOperationSearchOperation;
+import org.opends.server.types.operation.SearchEntrySearchOperation;
+import org.opends.server.types.operation.SearchReferenceSearchOperation;
+
+/**
+ * This class defines an operation used to search for entries in a local backend
+ * of the Directory Server.
+ */
+public class LocalBackendSearchOperation extends SearchOperationWrapper
+ implements PreOperationSearchOperation,
+ PostOperationSearchOperation,
+ SearchEntrySearchOperation,
+ SearchReferenceSearchOperation
+{
+
+ /**
+ * Creates a new operation that may be used to search for entries in a
+ * local backend of the Directory Server.
+ *
+ * @param search The operation to enhance.
+ */
+ public LocalBackendSearchOperation(SearchOperation search){
+ super(search);
+ LocalBackendWorkflowElement.attachLocalOperation(search, this);
+ }
+
+ /**
+ * Processes the search in the provided backend and recursively through its
+ * subordinate backends.
+ *
+ * @param backend The backend in which to process the search.
+ *
+ * @throws DirectoryException If a problem occurs while processing the
+ * search.
+ *
+ * @throws CancelledOperationException If the backend noticed and reacted
+ * to a request to cancel or abandon the
+ * search operation.
+ */
+ public final void searchBackend(Backend backend)
+ throws DirectoryException, CancelledOperationException
+ {
+ // Check for and handle a request to cancel this operation.
+ if (getCancelRequest() != null)
+ {
+ setCancelResult(CancelResult.CANCELED);
+ setProcessingStopTime();
+ return;
+ }
+
+ // Perform the search in the provided backend.
+ backend.search(this);
+
+ // Search in the subordinate backends is now done by the workflows.
+
+ // If there are any subordinate backends, then process the search there as
+ // well.
+ // FIXME jdemendi - From now on, do not search in the subordinate backends
+ // because this is done by the workflow topology.
+// Backend[] subBackends = backend.getSubordinateBackends();
+// for (Backend b : subBackends)
+// {
+// DN[] baseDNs = b.getBaseDNs();
+// for (DN dn : baseDNs)
+// {
+// if (dn.isDescendantOf(getBaseDN()))
+// {
+// searchBackend(b);
+// break;
+// }
+// }
+// }
+
+ }
+}
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
new file mode 100644
index 0000000..1fe984b
--- /dev/null
+++ b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
@@ -0,0 +1,6638 @@
+/*
+ * 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 static org.opends.server.config.ConfigConstants.*;
+import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
+import static org.opends.server.messages.CoreMessages.*;
+import static org.opends.server.messages.MessageHandler.getMessage;
+import static org.opends.server.util.ServerConstants.*;
+import static org.opends.server.util.StaticUtils.getExceptionMessage;
+import static org.opends.server.util.StaticUtils.secondsToTimeString;
+import static org.opends.server.util.StaticUtils.toLowerCase;
+
+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.Map;
+import java.util.concurrent.locks.Lock;
+
+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.SASLMechanismHandler;
+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.AuthorizationIdentityResponseControl;
+import org.opends.server.controls.GetEffectiveRights;
+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.MatchedValuesControl;
+import org.opends.server.controls.PasswordExpiredControl;
+import org.opends.server.controls.PasswordExpiringControl;
+import org.opends.server.controls.PasswordPolicyErrorType;
+import org.opends.server.controls.PasswordPolicyResponseControl;
+import org.opends.server.controls.PasswordPolicyWarningType;
+import org.opends.server.controls.PersistentSearchControl;
+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.BindOperation;
+import org.opends.server.core.DeleteOperation;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.PasswordPolicy;
+import org.opends.server.core.PasswordPolicyState;
+import org.opends.server.core.PersistentSearch;
+import org.opends.server.core.PluginConfigManager;
+import org.opends.server.core.SearchOperation;
+import org.opends.server.loggers.debug.DebugLogger;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.protocols.asn1.ASN1OctetString;
+import org.opends.server.types.LDAPException;
+import org.opends.server.schema.AuthPasswordSyntax;
+import org.opends.server.schema.BooleanSyntax;
+import org.opends.server.schema.UserPasswordSyntax;
+import org.opends.server.types.AcceptRejectWarn;
+import org.opends.server.types.AccountStatusNotificationType;
+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.CancelResult;
+import org.opends.server.types.CancelledOperationException;
+import org.opends.server.types.Control;
+import org.opends.server.types.DN;
+import org.opends.server.types.DebugLogLevel;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.Entry;
+import org.opends.server.types.ErrorLogCategory;
+import org.opends.server.types.ErrorLogSeverity;
+import org.opends.server.types.LockManager;
+import org.opends.server.types.Modification;
+import org.opends.server.types.ModificationType;
+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;
+import org.opends.server.util.Validator;
+import org.opends.server.workflowelement.LeafWorkflowElement;
+
+import static org.opends.server.loggers.ErrorLogger.*;
+import static org.opends.server.loggers.AccessLogger.*;
+
+/**
+ * This class defines a local backend workflow element; e-g an entity that
+ * handle the processing of an operation aginst a local backend.
+ */
+public class LocalBackendWorkflowElement extends LeafWorkflowElement
+{
+ /**
+ * The tracer object for the debug logger.
+ */
+ private static final DebugTracer TRACER = DebugLogger.getTracer();
+
+ // the backend associated to that workflow element
+ Backend backend;
+
+
+ /**
+ * Creates a new instance of the local backend workflow element.
+ *
+ * @param backend the backend associated to that workflow element
+ */
+ public LocalBackendWorkflowElement(
+ Backend backend
+ )
+ {
+ this.backend = backend;
+
+ if (this.backend != null)
+ {
+ isPrivate = this.backend.isPrivateBackend();
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void execute(Operation operation)
+ {
+ switch (operation.getOperationType())
+ {
+ case BIND:
+ processBind((BindOperation) operation);
+ break;
+ case SEARCH:
+ processSearch((SearchOperation) operation);
+ break;
+ case ADD:
+ processAdd((AddOperation) operation);
+ break;
+ case DELETE:
+ processDelete((DeleteOperation) operation);
+ break;
+ case MODIFY:
+ processModify((ModifyOperation) operation);
+ break;
+ default:
+ // jdemendi - temporary code, just make sure that we don't fell into
+ // that incomplete code...
+ Validator.ensureTrue(false);
+ break;
+ }
+ }
+
+
+ /**
+ * Perform a modify operation against a local backend.
+ *
+ * @param operation - The operation to perform
+ */
+ public void processModify(ModifyOperation operation)
+ {
+ LocalBackendModifyOperation localOperation =
+ new LocalBackendModifyOperation(operation);
+
+ processLocalModify(localOperation);
+ }
+
+ /**
+ * Perform a local modify operation against the local backend.
+ *
+ * @param operation - The operation to perform
+ */
+ private void processLocalModify(LocalBackendModifyOperation localOp)
+ {
+ ClientConnection clientConnection = localOp.getClientConnection();
+
+ // Get the plugin config manager that will be used for invoking plugins.
+ PluginConfigManager pluginConfigManager =
+ DirectoryServer.getPluginConfigManager();
+ boolean skipPostOperation = false;
+
+ // Create a labeled block of code that we can break out of if a problem is
+ // detected.
+ modifyProcessing:
+ {
+ DN entryDN = localOp.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.
+ List<Modification> modifications = localOp.getModifications();
+ if (modifications == null)
+ {
+ break modifyProcessing;
+ }
+
+ if (modifications.isEmpty())
+ {
+ localOp.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+ localOp.appendErrorMessage(
+ getMessage(MSGID_MODIFY_NO_MODIFICATIONS,
+ 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 ((! localOp.isInternalOperation()) &&
+ clientConnection.mustChangePassword())
+ {
+ DN authzDN = localOp.getAuthorizationDN();
+ if ((authzDN != null) && (! authzDN.equals(entryDN)))
+ {
+ // The user will not be allowed to do anything else before
+ // the password gets changed.
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+
+ int msgID = MSGID_MODIFY_MUST_CHANGE_PASSWORD;
+ localOp.appendErrorMessage(getMessage(msgID));
+ break modifyProcessing;
+ }
+ }
+
+
+ // Check for and handle a request to cancel this operation.
+ if (localOp.getCancelRequest() != null)
+ {
+ localOp.indicateCancelled(localOp.getCancelRequest());
+ localOp.setProcessingStopTime();
+ }
+
+ // 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)
+ {
+ localOp.setResultCode(DirectoryServer.getServerErrorResultCode());
+ localOp.appendErrorMessage(
+ getMessage(MSGID_MODIFY_CANNOT_LOCK_ENTRY,
+ String.valueOf(entryDN)));
+
+ skipPostOperation = true;
+ break modifyProcessing;
+ }
+
+
+ try
+ {
+ // Check for and handle a request to cancel this operation.
+ if (localOp.getCancelRequest() != null)
+ {
+ localOp.indicateCancelled(localOp.getCancelRequest());
+ localOp.setProcessingStopTime();
+ return;
+ }
+
+
+ // Get the entry to modify. If it does not exist, then fail.
+ Entry currentEntry = null;
+ try
+ {
+ currentEntry = backend.getEntry(entryDN);
+ localOp.setCurrentEntry(currentEntry);
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResultCode(de.getResultCode());
+ localOp.appendErrorMessage(de.getErrorMessage());
+ localOp.setMatchedDN(de.getMatchedDN());
+ localOp.setReferralURLs(de.getReferralURLs());
+
+ break modifyProcessing;
+ }
+
+ if (currentEntry == null)
+ {
+ localOp.setResultCode(ResultCode.NO_SUCH_OBJECT);
+ localOp.appendErrorMessage(getMessage(MSGID_MODIFY_NO_SUCH_ENTRY,
+ String.valueOf(entryDN)));
+
+ // See if one of the entry's ancestors exists.
+ DN parentDN = entryDN.getParentDNInSuffix();
+ while (parentDN != null)
+ {
+ try
+ {
+ if (DirectoryServer.entryExists(parentDN))
+ {
+ localOp.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.
+ boolean noOp = false;
+ LDAPPreReadRequestControl preReadRequest = null;
+ LDAPPostReadRequestControl postReadRequest = null;
+ List<Control> requestControls = localOp.getRequestControls();
+ if ((requestControls != null) && (! requestControls.isEmpty()))
+ {
+ for (int i=0; i < requestControls.size(); i++)
+ {
+ Control c = requestControls.get(i);
+ String oid = c.getOID();
+
+ 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);
+ }
+
+ localOp.setResultCode(ResultCode.valueOf(le.getResultCode()));
+ localOp.appendErrorMessage(le.getMessage());
+
+ break modifyProcessing;
+ }
+ }
+
+ try
+ {
+ // FIXME -- We need to determine whether the current user has
+ // permission to make this determination.
+ SearchFilter filter = assertControl.getSearchFilter();
+ if (! filter.matchesEntry(currentEntry))
+ {
+ localOp.setResultCode(ResultCode.ASSERTION_FAILED);
+
+ localOp.appendErrorMessage(
+ getMessage(MSGID_MODIFY_ASSERTION_FAILED,
+ String.valueOf(entryDN)));
+
+ break modifyProcessing;
+ }
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResultCode(ResultCode.PROTOCOL_ERROR);
+
+ int msgID = MSGID_MODIFY_CANNOT_PROCESS_ASSERTION_FILTER;
+ localOp.appendErrorMessage(getMessage(
+ msgID, String.valueOf(entryDN),
+ de.getErrorMessage()));
+
+ break modifyProcessing;
+ }
+ }
+ else if (oid.equals(OID_LDAP_NOOP_OPENLDAP_ASSIGNED))
+ {
+ noOp = true;
+ }
+ else if (oid.equals(OID_LDAP_READENTRY_PREREAD))
+ {
+ if (c instanceof LDAPAssertionRequestControl)
+ {
+ preReadRequest = (LDAPPreReadRequestControl) c;
+ }
+ else
+ {
+ try
+ {
+ preReadRequest = LDAPPreReadRequestControl.decodeControl(c);
+ requestControls.set(i, preReadRequest);
+ }
+ catch (LDAPException le)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, le);
+ }
+
+ localOp.setResultCode(ResultCode.valueOf(le.getResultCode()));
+ localOp.appendErrorMessage(le.getMessage());
+
+ break modifyProcessing;
+ }
+ }
+ }
+ else if (oid.equals(OID_LDAP_READENTRY_POSTREAD))
+ {
+ if (c instanceof LDAPAssertionRequestControl)
+ {
+ postReadRequest = (LDAPPostReadRequestControl) c;
+ }
+ else
+ {
+ try
+ {
+ postReadRequest = LDAPPostReadRequestControl.decodeControl(c);
+ requestControls.set(i, postReadRequest);
+ }
+ catch (LDAPException le)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, le);
+ }
+
+ localOp.setResultCode(ResultCode.valueOf(le.getResultCode()));
+ localOp.appendErrorMessage(le.getMessage());
+
+ break modifyProcessing;
+ }
+ }
+ }
+ 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,
+ localOp))
+ {
+ int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
+ localOp.appendErrorMessage(getMessage(msgID));
+ localOp.setResultCode(ResultCode.AUTHORIZATION_DENIED);
+ break modifyProcessing;
+ }
+
+
+ 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);
+ }
+
+ localOp.setResultCode(ResultCode.valueOf(le.getResultCode()));
+ localOp.appendErrorMessage(le.getMessage());
+
+ break modifyProcessing;
+ }
+ }
+
+
+ Entry authorizationEntry;
+ try
+ {
+ authorizationEntry = proxyControl.getAuthorizationEntry();
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResultCode(de.getResultCode());
+ localOp.appendErrorMessage(de.getErrorMessage());
+
+ break modifyProcessing;
+ }
+
+
+ if (AccessControlConfigManager.getInstance().
+ getAccessControlHandler().isProxiedAuthAllowed(localOp,
+ authorizationEntry) == false) {
+ localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+
+ int msgID = MSGID_MODIFY_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
+ localOp.appendErrorMessage(getMessage(msgID,
+ String.valueOf(entryDN)));
+
+ skipPostOperation = true;
+ break modifyProcessing;
+ }
+ localOp.setAuthorizationEntry(authorizationEntry);
+ if (authorizationEntry == null)
+ {
+ localOp.setProxiedAuthorizationDN(DN.nullDN());
+ }
+ else
+ {
+ localOp.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,
+ localOp))
+ {
+ int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
+ localOp.appendErrorMessage(getMessage(msgID));
+ localOp.setResultCode(ResultCode.AUTHORIZATION_DENIED);
+ break modifyProcessing;
+ }
+
+
+ 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);
+ }
+
+ localOp.setResultCode(ResultCode.valueOf(le.getResultCode()));
+ localOp.appendErrorMessage(le.getMessage());
+
+ break modifyProcessing;
+ }
+ }
+
+
+ Entry authorizationEntry;
+ try
+ {
+ authorizationEntry = proxyControl.getAuthorizationEntry();
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResultCode(de.getResultCode());
+ localOp.appendErrorMessage(de.getErrorMessage());
+
+ break modifyProcessing;
+ }
+
+ if (AccessControlConfigManager.getInstance().
+ getAccessControlHandler().isProxiedAuthAllowed(localOp,
+ authorizationEntry) == false) {
+ localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+
+ int msgID = MSGID_MODIFY_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
+ localOp.appendErrorMessage(getMessage(msgID,
+ String.valueOf(entryDN)));
+
+ skipPostOperation = true;
+ break modifyProcessing;
+ }
+ localOp.setAuthorizationEntry(authorizationEntry);
+ if (authorizationEntry == null)
+ {
+ localOp.setProxiedAuthorizationDN(DN.nullDN());
+ }
+ else
+ {
+ localOp.setProxiedAuthorizationDN(authorizationEntry.getDN());
+ }
+ }
+
+ // NYI -- Add support for additional controls.
+ else if (c.isCritical())
+ {
+ if ((backend == null) || (! backend.supportsControl(oid)))
+ {
+ localOp.setResultCode(
+ ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
+
+ int msgID = MSGID_MODIFY_UNSUPPORTED_CRITICAL_CONTROL;
+ localOp.appendErrorMessage(
+ getMessage(msgID, String.valueOf(entryDN),
+ oid));
+
+ 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.
+ PasswordPolicyState pwPolicyState;
+ boolean selfChange = entryDN.equals(localOp.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);
+ }
+
+ localOp.setResultCode(de.getResultCode());
+ localOp.appendErrorMessage(de.getErrorMessage());
+
+ break modifyProcessing;
+ }
+
+
+ // Create a duplicate of the entry and apply the changes to it.
+ Entry modifiedEntry = currentEntry.duplicate(false);
+ localOp.setModifiedEntry(modifiedEntry);
+
+ 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(localOp);
+ if (! result.continueOperationProcessing())
+ {
+ break modifyProcessing;
+ }
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ logError(ErrorLogCategory.SYNCHRONIZATION,
+ ErrorLogSeverity.SEVERE_ERROR,
+ MSGID_MODIFY_SYNCH_CONFLICT_RESOLUTION_FAILED,
+ localOp.getConnectionID(),
+ localOp.getOperationID(),
+ getExceptionMessage(de));
+
+ localOp.setResponseData(de);
+ break modifyProcessing;
+ }
+ }
+ }
+
+
+ // Declare variables used for password policy state processing.
+ boolean passwordChanged = false;
+ boolean currentPasswordProvided = false;
+ boolean isEnabled = true;
+ boolean enabledStateChanged = false;
+ int numPasswords;
+ 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 (! (localOp.isInternalOperation()
+ || localOp.isSynchronizationOperation()))
+ {
+ for (Modification m : modifications)
+ {
+ if (m.getAttribute().getAttributeType().equals(
+ pwPolicyState.getPolicy().getPasswordAttribute()))
+ {
+ passwordChanged = true;
+ if (! selfChange)
+ {
+ if (! clientConnection.hasPrivilege(
+ Privilege.PASSWORD_RESET,
+ localOp))
+ {
+ int msgID = MSGID_MODIFY_PWRESET_INSUFFICIENT_PRIVILEGES;
+ localOp.appendErrorMessage(getMessage(msgID));
+ localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+ break modifyProcessing;
+ }
+ }
+
+ 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 (! (localOp.isInternalOperation() ||
+ localOp.isSynchronizationOperation() ||
+ m.isInternal()))
+ {
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+ localOp.appendErrorMessage(
+ getMessage(MSGID_MODIFY_ATTR_IS_NO_USER_MOD,
+ String.valueOf(entryDN),
+ a.getName()));
+ break modifyProcessing;
+ }
+ }
+
+ // 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 (! (localOp.isInternalOperation() ||
+ localOp.isSynchronizationOperation() ||
+ m.isInternal()))
+ {
+ localOp.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+ localOp.appendErrorMessage(
+ getMessage(MSGID_MODIFY_ATTR_IS_OBSOLETE,
+ String.valueOf(entryDN),
+ a.getName()));
+ break modifyProcessing;
+ }
+ }
+ }
+
+
+ // 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,
+ localOp))
+ {
+ int msgID = MSGID_MODIFY_CHANGE_PRIVILEGE_INSUFFICIENT_PRIVILEGES;
+ localOp.appendErrorMessage(getMessage(msgID));
+ localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+ break modifyProcessing;
+ }
+ }
+
+
+ // 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 && (!(localOp.isSynchronizationOperation())))
+ {
+ // If the attribute contains any options, then reject it. Passwords
+ // will not be allowed to have options. Skipped for internal
+ // operations.
+ if(!localOp.isInternalOperation())
+ {
+ if (a.hasOptions())
+ {
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+
+ int msgID = MSGID_MODIFY_PASSWORDS_CANNOT_HAVE_OPTIONS;
+ localOp.appendErrorMessage(getMessage(msgID));
+ break modifyProcessing;
+ }
+
+
+ // If it's a self change, then see if that's allowed.
+ if (selfChange &&
+ (! pwPolicyState.getPolicy().allowUserPasswordChanges()))
+ {
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+
+ int msgID = MSGID_MODIFY_NO_USER_PW_CHANGES;
+ localOp.appendErrorMessage(getMessage(msgID));
+ break modifyProcessing;
+ }
+
+
+ // If we require secure password changes, then makes sure it's a
+ // secure communication channel.
+ if (pwPolicyState.getPolicy().requireSecurePasswordChanges() &&
+ (! clientConnection.isSecure()))
+ {
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+
+ int msgID = MSGID_MODIFY_REQUIRE_SECURE_CHANGES;
+ localOp.appendErrorMessage(getMessage(msgID));
+ break modifyProcessing;
+ }
+
+
+ // If it's a self change and it's not been long enough since the
+ // previous change, then reject it.
+ if (selfChange && pwPolicyState.isWithinMinimumAge())
+ {
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+
+ int msgID = MSGID_MODIFY_WITHIN_MINIMUM_AGE;
+ localOp.appendErrorMessage(getMessage(msgID));
+ break modifyProcessing;
+ }
+ }
+
+ // 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 = false;
+ LinkedHashSet<AttributeValue> pwValues = a.getValues();
+ LinkedHashSet<AttributeValue> encodedValues =
+ new LinkedHashSet<AttributeValue>();
+ switch (m.getModificationType())
+ {
+ case ADD:
+ case REPLACE:
+ int passwordsToAdd = pwValues.size();
+
+ if (m.getModificationType() == ModificationType.ADD)
+ {
+ numPasswords += passwordsToAdd;
+ isAdd = true;
+ }
+ else
+ {
+ numPasswords = passwordsToAdd;
+ }
+ // If there were multiple password values provided, then make
+ // sure that's OK.
+
+ if (! localOp.isInternalOperation() &&
+ ! pwPolicyState.getPolicy().allowExpiredPasswordChanges() &&
+ (passwordsToAdd > 1))
+ {
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+
+ int msgID = MSGID_MODIFY_MULTIPLE_VALUES_NOT_ALLOWED;
+ localOp.appendErrorMessage(getMessage(msgID));
+ break modifyProcessing;
+ }
+
+ // 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 ((!localOp.isInternalOperation()) &&
+ ! pwPolicyState.getPolicy().allowPreEncodedPasswords())
+ {
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+
+ int msgID = MSGID_MODIFY_NO_PREENCODED_PASSWORDS;
+ localOp.appendErrorMessage(getMessage(msgID));
+ break modifyProcessing;
+ }
+ else
+ {
+ encodedValues.add(v);
+ }
+ }
+ else
+ {
+ if (isAdd)
+ {
+ // Make sure that the password value doesn't already
+ // exist.
+ if (pwPolicyState.passwordMatches(v.getValue()))
+ {
+ localOp.setResultCode(
+ ResultCode.ATTRIBUTE_OR_VALUE_EXISTS);
+
+ int msgID = MSGID_MODIFY_PASSWORD_EXISTS;
+ localOp.appendErrorMessage(getMessage(msgID));
+ break modifyProcessing;
+ }
+ }
+
+ List<AttributeValue> newPasswords =
+ localOp.getNewPasswords() ;
+ if (newPasswords == null)
+ {
+ newPasswords = new LinkedList<AttributeValue>();
+ localOp.setNewPasswords(newPasswords);
+ }
+
+ newPasswords.add(v);
+
+ try
+ {
+ for (ByteString s :
+ pwPolicyState.encodePassword(v.getValue()))
+ {
+ encodedValues.add(new AttributeValue(
+ a.getAttributeType(), s));
+ }
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResultCode(de.getResultCode());
+ localOp.appendErrorMessage(de.getErrorMessage());
+ break modifyProcessing;
+ }
+ }
+ }
+
+ a.setValues(encodedValues);
+
+ break;
+
+ case DELETE:
+ // 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 ((!localOp.isInternalOperation()) && selfChange)
+ {
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+
+ int msgID = MSGID_MODIFY_NO_PREENCODED_PASSWORDS;
+ localOp.appendErrorMessage(getMessage(msgID));
+ break modifyProcessing;
+ }
+ else
+ {
+ encodedValues.add(v);
+ }
+ }
+ else
+ {
+ List<Attribute> attrList = currentEntry.getAttribute(t);
+ if ((attrList == null) || (attrList.isEmpty()))
+ {
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+
+ int msgID = MSGID_MODIFY_NO_EXISTING_VALUES;
+ localOp.appendErrorMessage(getMessage(msgID));
+ break modifyProcessing;
+ }
+ boolean found = false;
+ for (Attribute attr : attrList)
+ {
+ for (AttributeValue av : attr.getValues())
+ {
+ if (pwPolicyState.getPolicy().usesAuthPasswordSyntax())
+ {
+ if (AuthPasswordSyntax.isEncoded(av.getValue()))
+ {
+ try
+ {
+ 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;
+ }
+ }
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(
+ DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResultCode(de.getResultCode());
+
+ int msgID = MSGID_MODIFY_CANNOT_DECODE_PW;
+ localOp.appendErrorMessage(
+ getMessage(msgID, de.getErrorMessage()));
+ break modifyProcessing;
+ }
+ }
+ else
+ {
+ if (av.equals(v))
+ {
+ encodedValues.add(v);
+ found = true;
+ }
+ }
+ }
+ else
+ {
+ if (UserPasswordSyntax.isEncoded(av.getValue()))
+ {
+ try
+ {
+ 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;
+ }
+ }
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(
+ DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResultCode(de.getResultCode());
+
+ int msgID = MSGID_MODIFY_CANNOT_DECODE_PW;
+ localOp.appendErrorMessage(getMessage(msgID,
+ de.getErrorMessage()));
+ break modifyProcessing;
+ }
+ }
+ else
+ {
+ if (av.equals(v))
+ {
+ encodedValues.add(v);
+ found = true;
+ }
+ }
+ }
+ }
+ }
+
+ if (found)
+ {
+ List<AttributeValue> currentPasswords =
+ localOp.getCurrentPasswords();
+ if (currentPasswords == null)
+ {
+ currentPasswords = new LinkedList<AttributeValue>();
+ localOp.setCurrentPasswords(currentPasswords);
+ }
+ currentPasswords.add(v);
+
+ numPasswords--;
+ }
+ else
+ {
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+
+ int msgID = MSGID_MODIFY_INVALID_PASSWORD;
+ localOp.appendErrorMessage(getMessage(msgID));
+ break modifyProcessing;
+ }
+
+ currentPasswordProvided = true;
+ }
+ }
+
+ a.setValues(encodedValues);
+
+ break;
+
+ default:
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+
+ int msgID = MSGID_MODIFY_INVALID_MOD_TYPE_FOR_PASSWORD;
+ localOp.appendErrorMessage(getMessage(msgID,
+ String.valueOf(m.getModificationType()), a.getName()));
+
+ break modifyProcessing;
+ }
+ }
+ 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)
+ {
+ localOp.setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
+
+ int msgID = MSGID_MODIFY_INVALID_DISABLED_VALUE;
+ String message =
+ getMessage(msgID, OP_ATTR_ACCOUNT_DISABLED,
+ String.valueOf(de.getErrorMessage()));
+ localOp.appendErrorMessage(message);
+ break modifyProcessing;
+ }
+ }
+ }
+ }
+
+
+ switch (m.getModificationType())
+ {
+ case ADD:
+ // Make sure that one or more values have been provided for the
+ // attribute.
+ LinkedHashSet<AttributeValue> newValues = a.getValues();
+ if ((newValues == null) || newValues.isEmpty())
+ {
+ localOp.setResultCode(ResultCode.PROTOCOL_ERROR);
+ localOp.appendErrorMessage(getMessage(MSGID_MODIFY_ADD_NO_VALUES,
+ String.valueOf(entryDN),
+ a.getName()));
+ break modifyProcessing;
+ }
+
+
+ // Make sure that all the new values are valid according to the
+ // associated syntax.
+ if (DirectoryServer.checkSchema())
+ {
+ AcceptRejectWarn syntaxPolicy =
+ DirectoryServer.getSyntaxEnforcementPolicy();
+ AttributeSyntax syntax = t.getSyntax();
+
+ if (syntaxPolicy == AcceptRejectWarn.REJECT)
+ {
+ StringBuilder invalidReason =
+ new StringBuilder();
+
+ for (AttributeValue v : newValues)
+ {
+ if (! syntax.valueIsAcceptable(v.getValue(), invalidReason))
+ {
+ localOp.setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
+
+ int msgID = MSGID_MODIFY_ADD_INVALID_SYNTAX;
+ localOp.appendErrorMessage(getMessage(msgID,
+ String.valueOf(entryDN),
+ a.getName(),
+ v.getStringValue(),
+ invalidReason.toString()));
+
+ break modifyProcessing;
+ }
+ }
+ }
+ else if (syntaxPolicy == AcceptRejectWarn.WARN)
+ {
+ StringBuilder invalidReason = new StringBuilder();
+
+ for (AttributeValue v : newValues)
+ {
+ if (! syntax.valueIsAcceptable(v.getValue(), invalidReason))
+ {
+ localOp.setResultCode(
+ ResultCode.INVALID_ATTRIBUTE_SYNTAX);
+
+ int msgID = MSGID_MODIFY_ADD_INVALID_SYNTAX;
+ logError(ErrorLogCategory.SCHEMA,
+ ErrorLogSeverity.SEVERE_WARNING, msgID,
+ String.valueOf(entryDN), a.getName(),
+ v.getStringValue(), invalidReason.toString());
+
+ invalidReason = new StringBuilder();
+ }
+ }
+ }
+ }
+
+
+ // Add the provided attribute or merge an existing attribute with
+ // the values of the new attribute. If there are any duplicates,
+ // then fail.
+ LinkedList<AttributeValue> duplicateValues =
+ new LinkedList<AttributeValue>();
+ if (a.getAttributeType().isObjectClassType())
+ {
+ try
+ {
+ modifiedEntry.addObjectClasses(newValues);
+ break;
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResponseData(de);
+ break modifyProcessing;
+ }
+ }
+ else
+ {
+ modifiedEntry.addAttribute(a, duplicateValues);
+ if (duplicateValues.isEmpty())
+ {
+ break;
+ }
+ else
+ {
+ StringBuilder buffer = new StringBuilder();
+ Iterator<AttributeValue> iterator =
+ duplicateValues.iterator();
+ buffer.append(iterator.next().getStringValue());
+ while (iterator.hasNext())
+ {
+ buffer.append(", ");
+ buffer.append(iterator.next().getStringValue());
+ }
+
+ localOp.setResultCode(ResultCode.ATTRIBUTE_OR_VALUE_EXISTS);
+
+ int msgID = MSGID_MODIFY_ADD_DUPLICATE_VALUE;
+ localOp.appendErrorMessage(
+ getMessage(msgID, String.valueOf(entryDN),
+ a.getName(),
+ buffer.toString()));
+
+ break modifyProcessing;
+ }
+ }
+
+
+ case DELETE:
+ // 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(a, missingValues);
+
+ if (attrExists)
+ {
+ if (missingValues.isEmpty())
+ {
+ RDN rdn = modifiedEntry.getDN().getRDN();
+ if ((rdn != null) && rdn.hasAttributeType(t) &&
+ (! modifiedEntry.hasValue(t, a.getOptions(),
+ rdn.getAttributeValue(t))))
+ {
+ localOp.setResultCode(ResultCode.NOT_ALLOWED_ON_RDN);
+
+ int msgID = MSGID_MODIFY_DELETE_RDN_ATTR;
+ localOp.appendErrorMessage(getMessage(msgID,
+ String.valueOf(entryDN),
+ a.getName()));
+ break modifyProcessing;
+ }
+
+ break;
+ }
+ 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());
+ }
+
+ localOp.setResultCode(ResultCode.NO_SUCH_ATTRIBUTE);
+
+ int msgID = MSGID_MODIFY_DELETE_MISSING_VALUES;
+ localOp.appendErrorMessage(
+ getMessage(msgID, String.valueOf(entryDN),
+ a.getName(),
+ buffer.toString()));
+
+ break modifyProcessing;
+ }
+ }
+ else
+ {
+ localOp.setResultCode(ResultCode.NO_SUCH_ATTRIBUTE);
+
+ int msgID = MSGID_MODIFY_DELETE_NO_SUCH_ATTR;
+ localOp.appendErrorMessage(
+ getMessage(msgID, String.valueOf(entryDN),
+ a.getName()));
+ break modifyProcessing;
+ }
+
+
+ case REPLACE:
+ // If it is the objectclass attribute, then treat that separately.
+ if (a.getAttributeType().isObjectClassType())
+ {
+ try
+ {
+ modifiedEntry.setObjectClasses(a.getValues());
+ break;
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResponseData(de);
+ break modifyProcessing;
+ }
+ }
+
+
+ // If the provided attribute does not have any values, then we
+ // will simply remove the attribute from the entry (if it exists).
+ if (! a.hasValue())
+ {
+ modifiedEntry.removeAttribute(t, a.getOptions());
+ RDN rdn = modifiedEntry.getDN().getRDN();
+ if ((rdn != null) && rdn.hasAttributeType(t) &&
+ (! modifiedEntry.hasValue(t, a.getOptions(),
+ rdn.getAttributeValue(t))))
+ {
+ localOp.setResultCode(ResultCode.NOT_ALLOWED_ON_RDN);
+
+ int msgID = MSGID_MODIFY_DELETE_RDN_ATTR;
+ localOp.appendErrorMessage(
+ getMessage(msgID, String.valueOf(entryDN),
+ a.getName()));
+ break modifyProcessing;
+ }
+ break;
+ }
+
+
+ // Make sure that all the new values are valid according to the
+ // associated syntax.
+ newValues = a.getValues();
+ if (DirectoryServer.checkSchema())
+ {
+ AcceptRejectWarn syntaxPolicy =
+ DirectoryServer.getSyntaxEnforcementPolicy();
+ AttributeSyntax syntax = t.getSyntax();
+
+ if (syntaxPolicy == AcceptRejectWarn.REJECT)
+ {
+ StringBuilder invalidReason = new StringBuilder();
+
+ for (AttributeValue v : newValues)
+ {
+ if (! syntax.valueIsAcceptable(v.getValue(), invalidReason))
+ {
+ localOp.setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
+
+ int msgID = MSGID_MODIFY_REPLACE_INVALID_SYNTAX;
+ localOp.appendErrorMessage(getMessage(msgID,
+ String.valueOf(entryDN),
+ a.getName(),
+ v.getStringValue(),
+ invalidReason.toString()));
+
+ break modifyProcessing;
+ }
+ }
+ }
+ else if (syntaxPolicy == AcceptRejectWarn.WARN)
+ {
+ StringBuilder invalidReason = new StringBuilder();
+
+ for (AttributeValue v : newValues)
+ {
+ if (! syntax.valueIsAcceptable(v.getValue(), invalidReason))
+ {
+ localOp.setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
+
+ int msgID = MSGID_MODIFY_REPLACE_INVALID_SYNTAX;
+ logError(ErrorLogCategory.SCHEMA,
+ ErrorLogSeverity.SEVERE_WARNING, msgID,
+ String.valueOf(entryDN), a.getName(),
+ v.getStringValue(), invalidReason.toString());
+
+ invalidReason = new StringBuilder();
+ }
+ }
+ }
+ }
+
+
+ // 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 (! a.hasOptions())
+ {
+ List<Attribute> attrList = new ArrayList<Attribute>(1);
+ attrList.add(a);
+ modifiedEntry.putAttribute(t, attrList);
+
+ RDN rdn = modifiedEntry.getDN().getRDN();
+ if ((rdn != null) && rdn.hasAttributeType(t) &&
+ (! modifiedEntry.hasValue(t, a.getOptions(),
+ rdn.getAttributeValue(t))))
+ {
+ localOp.setResultCode(ResultCode.NOT_ALLOWED_ON_RDN);
+
+ int msgID = MSGID_MODIFY_DELETE_RDN_ATTR;
+ localOp.appendErrorMessage(
+ getMessage(msgID, String.valueOf(entryDN),
+ a.getName()));
+ break modifyProcessing;
+ }
+ break;
+ }
+
+
+ // 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(a);
+ modifiedEntry.putAttribute(t, attrList);
+
+ RDN rdn = modifiedEntry.getDN().getRDN();
+ if ((rdn != null) && rdn.hasAttributeType(t) &&
+ (! modifiedEntry.hasValue(t, a.getOptions(),
+ rdn.getAttributeValue(t))))
+ {
+ localOp.setResultCode(ResultCode.NOT_ALLOWED_ON_RDN);
+
+ int msgID = MSGID_MODIFY_DELETE_RDN_ATTR;
+ localOp.appendErrorMessage(
+ getMessage(msgID, String.valueOf(entryDN),
+ a.getName()));
+ break modifyProcessing;
+ }
+ break;
+ }
+
+
+ // 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(a.getOptions()))
+ {
+ attrList.set(i, a);
+ found = true;
+ break;
+ }
+ }
+
+ if (! found)
+ {
+ attrList.add(a);
+ }
+
+ RDN rdn = modifiedEntry.getDN().getRDN();
+ if ((rdn != null) && rdn.hasAttributeType(t) &&
+ (! modifiedEntry.hasValue(t, a.getOptions(),
+ rdn.getAttributeValue(t))))
+ {
+ localOp.setResultCode(ResultCode.NOT_ALLOWED_ON_RDN);
+
+ int msgID = MSGID_MODIFY_DELETE_RDN_ATTR;
+ localOp.appendErrorMessage(
+ getMessage(msgID, String.valueOf(entryDN),
+ a.getName()));
+ break modifyProcessing;
+ }
+ break;
+
+
+ case INCREMENT:
+ // The specified attribute type must not be an RDN attribute.
+ rdn = modifiedEntry.getDN().getRDN();
+ if ((rdn != null) && rdn.hasAttributeType(t))
+ {
+ localOp.setResultCode(ResultCode.NOT_ALLOWED_ON_RDN);
+ localOp.appendErrorMessage(getMessage(MSGID_MODIFY_INCREMENT_RDN,
+ String.valueOf(entryDN),
+ a.getName()));
+ }
+
+
+ // The provided attribute must have a single value, and it must be
+ // an integer.
+ LinkedHashSet<AttributeValue> values = a.getValues();
+ if ((values == null) || values.isEmpty())
+ {
+ localOp.setResultCode(ResultCode.PROTOCOL_ERROR);
+
+ int msgID = MSGID_MODIFY_INCREMENT_REQUIRES_VALUE;
+ localOp.appendErrorMessage(
+ getMessage(msgID, String.valueOf(entryDN),
+ a.getName()));
+
+ break modifyProcessing;
+ }
+ else if (values.size() > 1)
+ {
+ localOp.setResultCode(ResultCode.PROTOCOL_ERROR);
+
+ int msgID = MSGID_MODIFY_INCREMENT_REQUIRES_SINGLE_VALUE;
+ localOp.appendErrorMessage(
+ getMessage(msgID, String.valueOf(entryDN),
+ a.getName()));
+ break modifyProcessing;
+ }
+
+ AttributeValue v = values.iterator().next();
+
+ long incrementValue;
+ try
+ {
+ incrementValue = Long.parseLong(v.getNormalizedStringValue());
+ }
+ catch (Exception e)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+
+ localOp.setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
+
+ int msgID = MSGID_MODIFY_INCREMENT_PROVIDED_VALUE_NOT_INTEGER;
+ localOp.appendErrorMessage(
+ getMessage(msgID, String.valueOf(entryDN),
+ a.getName(), v.getStringValue()));
+
+ break modifyProcessing;
+ }
+
+
+ // Get the corresponding attribute from the entry and make sure
+ // that it has a single integer value.
+ attrList = modifiedEntry.getAttribute(t, a.getOptions());
+ if ((attrList == null) || attrList.isEmpty())
+ {
+ localOp.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+
+ int msgID = MSGID_MODIFY_INCREMENT_REQUIRES_EXISTING_VALUE;
+ localOp.appendErrorMessage(
+ getMessage(msgID, String.valueOf(entryDN),
+ a.getName()));
+
+ break modifyProcessing;
+ }
+
+ boolean updated = false;
+ for (Attribute attr : attrList)
+ {
+ LinkedHashSet<AttributeValue> valueList = attr.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);
+ }
+
+ localOp.setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
+
+ int msgID = MSGID_MODIFY_INCREMENT_REQUIRES_INTEGER_VALUE;
+ localOp.appendErrorMessage(getMessage(msgID,
+ String.valueOf(entryDN),
+ a.getName(),
+ existingValue.getStringValue()));
+ break modifyProcessing;
+ }
+
+ ByteString newValue =
+ new ASN1OctetString(String.valueOf(newIntValue));
+ newValueList.add(new AttributeValue(t, newValue));
+ }
+
+ attr.setValues(newValueList);
+ updated = true;
+ }
+
+ if (! updated)
+ {
+ localOp.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+
+ int msgID = MSGID_MODIFY_INCREMENT_REQUIRES_EXISTING_VALUE;
+ localOp.appendErrorMessage(
+ getMessage(msgID, String.valueOf(entryDN),
+ a.getName()));
+
+ break modifyProcessing;
+ }
+
+ break;
+
+ default:
+ }
+ }
+
+
+ // If there was a password change, then perform any additional checks
+ // that may be necessary.
+ if (passwordChanged)
+ {
+ // If it was a self change, then see if the current password was
+ // provided and handle accordingly.
+ if (selfChange &&
+ pwPolicyState.getPolicy().requireCurrentPassword() &&
+ (! currentPasswordProvided))
+ {
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+
+ int msgID = MSGID_MODIFY_PW_CHANGE_REQUIRES_CURRENT_PW;
+ localOp.appendErrorMessage(getMessage(msgID));
+ break modifyProcessing;
+ }
+
+
+ // If this change would result in multiple password values, then see
+ // if that's OK.
+ if ((numPasswords > 1) &&
+ (! pwPolicyState.getPolicy().allowMultiplePasswordValues()))
+ {
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+
+ int msgID = MSGID_MODIFY_MULTIPLE_PASSWORDS_NOT_ALLOWED;
+ localOp.appendErrorMessage(getMessage(msgID));
+ break modifyProcessing;
+ }
+
+
+ // If any of the password values should be validated, then do so now.
+ if (selfChange ||
+ (! pwPolicyState.getPolicy().skipValidationForAdministrators()))
+ {
+ List<AttributeValue> newPasswords =
+ localOp.getNewPasswords();
+ List<AttributeValue> currentPasswords =
+ localOp.getCurrentPasswords();
+
+ 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)
+ {
+ StringBuilder invalidReason = new StringBuilder();
+ if (! pwPolicyState.passwordIsAcceptable(localOp, modifiedEntry,
+ v.getValue(),
+ clearPasswords,
+ invalidReason))
+ {
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+
+ int msgID = MSGID_MODIFY_PW_VALIDATION_FAILED;
+ localOp.appendErrorMessage(getMessage(msgID,
+ invalidReason.toString()));
+ 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 permission
+ // 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(localOp)) {
+ localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+
+ int msgID = MSGID_MODIFY_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
+ localOp.appendErrorMessage(
+ getMessage(msgID, String.valueOf(entryDN)));
+
+ skipPostOperation = true;
+ break modifyProcessing;
+ }
+
+ boolean wasLocked = false;
+ if (passwordChanged)
+ {
+ // 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
+ {
+ pwPolicyState.setMustChangePassword(
+ pwPolicyState.getPolicy().forceChangeOnReset());
+ }
+ }
+
+ if (pwPolicyState.getPolicy().getRequireChangeByTime() > 0)
+ {
+ pwPolicyState.setRequiredChangeTime();
+ }
+ modifications.addAll(pwPolicyState.getModifications());
+ //Apply pwd Policy modifications to modified entry.
+ try {
+ modifiedEntry.applyModifications(pwPolicyState.getModifications());
+ } catch (DirectoryException e) {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+
+ localOp.setResponseData(e);
+ break modifyProcessing;
+ }
+ }
+ else if((! localOp.isInternalOperation()) &&
+ pwPolicyState.mustChangePassword())
+ {
+ // The user will not be allowed to do anything else before
+ // the password gets changed.
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+
+ int msgID = MSGID_MODIFY_MUST_CHANGE_PASSWORD;
+ localOp.appendErrorMessage(getMessage(msgID));
+ break modifyProcessing;
+ }
+
+ // Make sure that the new entry is valid per the server schema.
+ if (DirectoryServer.checkSchema())
+ {
+ StringBuilder invalidReason = new StringBuilder();
+ if (! modifiedEntry.conformsToSchema(null, false, false, false,
+ invalidReason))
+ {
+ localOp.setResultCode(ResultCode.OBJECTCLASS_VIOLATION);
+ localOp.appendErrorMessage(getMessage(MSGID_MODIFY_VIOLATES_SCHEMA,
+ String.valueOf(entryDN),
+ invalidReason.toString()));
+ break modifyProcessing;
+ }
+ }
+
+
+ // Check for and handle a request to cancel this operation.
+ if (localOp.getCancelRequest() != null)
+ {
+ localOp.indicateCancelled(localOp.getCancelRequest());
+ localOp.setProcessingStopTime();
+ return;
+ }
+
+ // If the operation is not a synchronization operation,
+ // Invoke the pre-operation modify plugins.
+ if (!localOp.isSynchronizationOperation())
+ {
+ PreOperationPluginResult preOpResult =
+ pluginConfigManager.invokePreOperationModifyPlugins(localOp);
+ if (preOpResult.connectionTerminated())
+ {
+ // There's no point in continuing with anything. Log the result
+ // and return.
+ localOp.setResultCode(ResultCode.CANCELED);
+
+ int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
+ localOp.appendErrorMessage(getMessage(msgID));
+
+ localOp.setProcessingStopTime();
+
+ return;
+ }
+ else if (preOpResult.sendResponseImmediately())
+ {
+ skipPostOperation = true;
+ break modifyProcessing;
+ }
+ else if (preOpResult.skipCoreProcessing())
+ {
+ skipPostOperation = false;
+ break modifyProcessing;
+ }
+ }
+
+
+ // Check for and handle a request to cancel this operation.
+ if (localOp.getCancelRequest() != null)
+ {
+ localOp.indicateCancelled(localOp.getCancelRequest());
+ localOp.setProcessingStopTime();
+ return;
+ }
+
+
+ // Actually perform the modify operation. This should also include
+ // taking care of any synchronization that might be needed.
+ if (backend == null)
+ {
+ localOp.setResultCode(ResultCode.NO_SUCH_OBJECT);
+ localOp.appendErrorMessage(
+ getMessage(MSGID_MODIFY_NO_BACKEND_FOR_ENTRY,
+ String.valueOf(entryDN)));
+ break modifyProcessing;
+ }
+
+ try
+ {
+ // 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:
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+ localOp.appendErrorMessage(
+ getMessage(MSGID_MODIFY_SERVER_READONLY,
+ String.valueOf(entryDN)));
+ break modifyProcessing;
+
+ case INTERNAL_ONLY:
+ if (! (localOp.isInternalOperation() ||
+ localOp.isSynchronizationOperation()))
+ {
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+ localOp.appendErrorMessage(
+ getMessage(MSGID_MODIFY_SERVER_READONLY,
+ String.valueOf(entryDN)));
+ break modifyProcessing;
+ }
+ }
+
+ switch (backend.getWritabilityMode())
+ {
+ case DISABLED:
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+ localOp.appendErrorMessage(
+ getMessage(MSGID_MODIFY_BACKEND_READONLY,
+ String.valueOf(entryDN)));
+ break modifyProcessing;
+
+ case INTERNAL_ONLY:
+ if (! localOp.isInternalOperation() ||
+ localOp.isSynchronizationOperation())
+ {
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+ localOp.appendErrorMessage(
+ getMessage(MSGID_MODIFY_BACKEND_READONLY,
+ String.valueOf(entryDN)));
+ break modifyProcessing;
+ }
+ }
+ }
+
+
+ if (noOp)
+ {
+ localOp.appendErrorMessage(getMessage(MSGID_MODIFY_NOOP));
+
+ // FIXME -- We must set a result code other than SUCCESS.
+ }
+ else
+ {
+ for (SynchronizationProvider provider :
+ DirectoryServer.getSynchronizationProviders())
+ {
+ try
+ {
+ SynchronizationProviderResult result =
+ provider.doPreOperation(localOp);
+ if (! result.continueOperationProcessing())
+ {
+ break modifyProcessing;
+ }
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ logError(ErrorLogCategory.SYNCHRONIZATION,
+ ErrorLogSeverity.SEVERE_ERROR,
+ MSGID_MODIFY_SYNCH_PREOP_FAILED,
+ localOp.getConnectionID(),
+ localOp.getOperationID(),
+ getExceptionMessage(de));
+
+ localOp.setResponseData(de);
+ break modifyProcessing;
+ }
+ }
+
+ backend.replaceEntry(modifiedEntry, localOp);
+
+
+ // If the modification was successful, then see if there's any other
+ // work that we need to do here before handing off to postop
+ // plugins.
+ if (passwordChanged)
+ {
+ if (selfChange)
+ {
+ AuthenticationInfo authInfo =
+ clientConnection.getAuthenticationInfo();
+ if (authInfo.getAuthenticationDN().equals(entryDN))
+ {
+ clientConnection.setMustChangePassword(false);
+ }
+
+ int msgID = MSGID_MODIFY_PASSWORD_CHANGED;
+ String message = getMessage(msgID);
+ pwPolicyState.generateAccountStatusNotification(
+ AccountStatusNotificationType.PASSWORD_CHANGED, entryDN,
+ msgID, message);
+ }
+ else
+ {
+ int msgID = MSGID_MODIFY_PASSWORD_RESET;
+ String message = getMessage(msgID);
+ pwPolicyState.generateAccountStatusNotification(
+ AccountStatusNotificationType.PASSWORD_RESET, entryDN,
+ msgID, message);
+ }
+ }
+
+ if (enabledStateChanged)
+ {
+ if (isEnabled)
+ {
+ int msgID = MSGID_MODIFY_ACCOUNT_ENABLED;
+ String message = getMessage(msgID);
+ pwPolicyState.generateAccountStatusNotification(
+ AccountStatusNotificationType.ACCOUNT_ENABLED, entryDN,
+ msgID, message);
+ }
+ else
+ {
+ int msgID = MSGID_MODIFY_ACCOUNT_DISABLED;
+ String message = getMessage(msgID);
+ pwPolicyState.generateAccountStatusNotification(
+ AccountStatusNotificationType.ACCOUNT_DISABLED, entryDN,
+ msgID, message);
+ }
+ }
+
+ if (wasLocked)
+ {
+ int msgID = MSGID_MODIFY_ACCOUNT_UNLOCKED;
+ String message = getMessage(msgID);
+ pwPolicyState.generateAccountStatusNotification(
+ AccountStatusNotificationType.ACCOUNT_UNLOCKED, entryDN,
+ msgID, message);
+ }
+ }
+
+ 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);
+
+ localOp.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);
+
+ localOp.getResponseControls().add(responseControl);
+ }
+
+ localOp.setResultCode(ResultCode.SUCCESS);
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResultCode(de.getResultCode());
+ localOp.appendErrorMessage(de.getErrorMessage());
+ localOp.setMatchedDN(de.getMatchedDN());
+ localOp.setReferralURLs(de.getReferralURLs());
+
+ break modifyProcessing;
+ }
+ catch (CancelledOperationException coe)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, coe);
+ }
+
+ CancelResult cancelResult = coe.getCancelResult();
+
+ localOp.setCancelResult(cancelResult);
+ localOp.setResultCode(cancelResult.getResultCode());
+
+ String message = coe.getMessage();
+ if ((message != null) && (message.length() > 0))
+ {
+ localOp.appendErrorMessage(message);
+ }
+
+ break modifyProcessing;
+ }
+ }
+ finally
+ {
+ LockManager.unlock(entryDN, entryLock);
+
+ for (SynchronizationProvider provider :
+ DirectoryServer.getSynchronizationProviders())
+ {
+ try
+ {
+ provider.doPostOperation(localOp);
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ logError(ErrorLogCategory.SYNCHRONIZATION,
+ ErrorLogSeverity.SEVERE_ERROR,
+ MSGID_MODIFY_SYNCH_POSTOP_FAILED, localOp.getConnectionID(),
+ localOp.getOperationID(), getExceptionMessage(de));
+
+ localOp.setResponseData(de);
+ break;
+ }
+ }
+ }
+ }
+
+
+ // Indicate that it is now too late to attempt to cancel the operation.
+ localOp.setCancelResult(CancelResult.TOO_LATE);
+
+ // Invoke the post-operation modify plugins.
+ if (! skipPostOperation)
+ {
+ // FIXME -- Should this also be done while holding the locks?
+ PostOperationPluginResult postOpResult =
+ pluginConfigManager.invokePostOperationModifyPlugins(localOp);
+ if (postOpResult.connectionTerminated())
+ {
+ // There's no point in continuing with anything. Log the result and
+ // return.
+ localOp.setResultCode(ResultCode.CANCELED);
+
+ int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
+ localOp.appendErrorMessage(getMessage(msgID));
+
+ localOp.setProcessingStopTime();
+
+ return;
+ }
+ }
+
+ // Notify any change notification listeners that might be registered with
+ // the server.
+ if (localOp.getResultCode() == ResultCode.SUCCESS)
+ {
+ for (ChangeNotificationListener changeListener :
+ DirectoryServer.getChangeNotificationListeners())
+ {
+ try
+ {
+ changeListener.handleModifyOperation(localOp,
+ localOp.getCurrentEntry(),
+ localOp.getModifiedEntry());
+ }
+ catch (Exception e)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+
+ int msgID = MSGID_MODIFY_ERROR_NOTIFYING_CHANGE_LISTENER;
+ String message = getMessage(msgID, getExceptionMessage(e));
+ logError(ErrorLogCategory.CORE_SERVER, ErrorLogSeverity.SEVERE_ERROR,
+ message, msgID);
+ }
+ }
+ }
+
+
+
+ // Stop the processing timer.
+ localOp.setProcessingStopTime();
+ }
+
+ /**
+ * Perform a search operation against a local backend.
+ *
+ * @param operation - The operation to perform
+ */
+ public void processSearch(SearchOperation operation)
+ {
+
+ LocalBackendSearchOperation localOp =
+ new LocalBackendSearchOperation(operation);
+
+ PersistentSearch persistentSearch = null;
+
+ ClientConnection clientConnection = localOp.getClientConnection();
+
+ // Get the plugin config manager that will be used for invoking plugins.
+ PluginConfigManager pluginConfigManager =
+ DirectoryServer.getPluginConfigManager();
+ boolean skipPostOperation = false;
+
+ // Create a labeled block of code that we can break out of if a problem is
+ // detected.
+ searchProcessing:
+ {
+ // Process the search base and filter to convert them from their raw forms
+ // as provided by the client to the forms required for the rest of the
+ // search processing.
+ DN baseDN = localOp.getBaseDN();
+ SearchFilter filter = localOp.getFilter();
+
+ if ((baseDN == null) || (filter == null)){
+ break searchProcessing;
+ }
+
+ // Check to see if there are any controls in the request. If so, then
+ // see if there is any special processing required.
+ boolean processSearch = true;
+ List<Control> requestControls = localOp.getRequestControls();
+ if ((requestControls != null) && (! requestControls.isEmpty()))
+ {
+ for (int i=0; i < requestControls.size(); i++)
+ {
+ Control c = requestControls.get(i);
+ String oid = c.getOID();
+
+ 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);
+ }
+
+ localOp.setResultCode(
+ ResultCode.valueOf(le.getResultCode()));
+ localOp.appendErrorMessage(le.getMessage());
+
+ break searchProcessing;
+ }
+ }
+
+ try
+ {
+ // FIXME -- We need to determine whether the current user has
+ // permission to make this determination.
+ SearchFilter assertionFilter = assertControl.getSearchFilter();
+ Entry entry;
+ try
+ {
+ entry = DirectoryServer.getEntry(baseDN);
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResultCode(de.getResultCode());
+
+ int msgID = MSGID_SEARCH_CANNOT_GET_ENTRY_FOR_ASSERTION;
+ localOp.appendErrorMessage(
+ getMessage(msgID, de.getErrorMessage()));
+
+ break searchProcessing;
+ }
+
+ if (entry == null)
+ {
+ localOp.setResultCode(ResultCode.NO_SUCH_OBJECT);
+
+ int msgID = MSGID_SEARCH_NO_SUCH_ENTRY_FOR_ASSERTION;
+ localOp.appendErrorMessage(getMessage(msgID));
+
+ break searchProcessing;
+ }
+
+
+ if (! assertionFilter.matchesEntry(entry))
+ {
+ localOp.setResultCode(ResultCode.ASSERTION_FAILED);
+
+ localOp.appendErrorMessage(
+ getMessage(MSGID_SEARCH_ASSERTION_FAILED));
+
+ break searchProcessing;
+ }
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResultCode(ResultCode.PROTOCOL_ERROR);
+
+ int msgID = MSGID_SEARCH_CANNOT_PROCESS_ASSERTION_FILTER;
+ localOp.appendErrorMessage(
+ getMessage(msgID, de.getErrorMessage()));
+
+ break searchProcessing;
+ }
+ }
+ 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, localOp))
+ {
+ int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
+ localOp.appendErrorMessage(getMessage(msgID));
+ localOp.setResultCode(ResultCode.AUTHORIZATION_DENIED);
+ break searchProcessing;
+ }
+
+
+ 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);
+ }
+
+ localOp.setResultCode(
+ ResultCode.valueOf(le.getResultCode()));
+ localOp.appendErrorMessage(le.getMessage());
+
+ break searchProcessing;
+ }
+ }
+
+
+ Entry authorizationEntry;
+ try
+ {
+ authorizationEntry = proxyControl.getAuthorizationEntry();
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResultCode(de.getResultCode());
+ localOp.appendErrorMessage(de.getErrorMessage());
+
+ break searchProcessing;
+ }
+
+ if (AccessControlConfigManager.getInstance().
+ getAccessControlHandler().isProxiedAuthAllowed(localOp,
+ authorizationEntry) == false) {
+ localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+
+ int msgID = MSGID_SEARCH_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
+ localOp.appendErrorMessage(getMessage(msgID,
+ String.valueOf(baseDN)));
+
+ skipPostOperation = true;
+ break searchProcessing;
+ }
+ localOp.setAuthorizationEntry(authorizationEntry);
+ if (authorizationEntry == null)
+ {
+ localOp.setProxiedAuthorizationDN(DN.nullDN());
+ }
+ else
+ {
+ localOp.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, localOp))
+ {
+ int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
+ localOp.appendErrorMessage(getMessage(msgID));
+ localOp.setResultCode(ResultCode.AUTHORIZATION_DENIED);
+ break searchProcessing;
+ }
+
+
+ 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);
+ }
+
+ localOp.setResultCode(
+ ResultCode.valueOf(le.getResultCode()));
+ localOp.appendErrorMessage(le.getMessage());
+
+ break searchProcessing;
+ }
+ }
+
+
+ Entry authorizationEntry;
+ try
+ {
+ authorizationEntry = proxyControl.getAuthorizationEntry();
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResultCode(de.getResultCode());
+ localOp.appendErrorMessage(de.getErrorMessage());
+
+ break searchProcessing;
+ }
+
+ if (AccessControlConfigManager.getInstance().
+ getAccessControlHandler().isProxiedAuthAllowed(localOp,
+ authorizationEntry) == false) {
+ localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+
+ int msgID = MSGID_SEARCH_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
+ localOp.appendErrorMessage(getMessage(msgID,
+ String.valueOf(baseDN)));
+
+ skipPostOperation = true;
+ break searchProcessing;
+ }
+ localOp.setAuthorizationEntry(authorizationEntry);
+ if (authorizationEntry == null)
+ {
+ localOp.setProxiedAuthorizationDN(DN.nullDN());
+ }
+ else
+ {
+ localOp.setProxiedAuthorizationDN(authorizationEntry.getDN());
+ }
+ }
+ else if (oid.equals(OID_PERSISTENT_SEARCH))
+ {
+ PersistentSearchControl psearchControl;
+ if (c instanceof PersistentSearchControl)
+ {
+ psearchControl = (PersistentSearchControl) c;
+ }
+ else
+ {
+ try
+ {
+ psearchControl = PersistentSearchControl.decodeControl(c);
+ }
+ catch (LDAPException le)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, le);
+ }
+
+ localOp.setResultCode(
+ ResultCode.valueOf(le.getResultCode()));
+ localOp.appendErrorMessage(le.getMessage());
+
+ break searchProcessing;
+ }
+ }
+
+ persistentSearch =
+ new PersistentSearch(operation, psearchControl.getChangeTypes(),
+ psearchControl.getReturnECs());
+ localOp.setPersistentSearch(persistentSearch);
+
+ // If we're only interested in changes, then we don't actually want
+ // to process the search now.
+ if (psearchControl.getChangesOnly())
+ {
+ processSearch = false;
+ }
+ }
+ else if (oid.equals(OID_LDAP_SUBENTRIES))
+ {
+ localOp.setReturnLDAPSubentries(true);
+ }
+ else if (oid.equals(OID_MATCHED_VALUES))
+ {
+ if (c instanceof MatchedValuesControl)
+ {
+ localOp.setMatchedValuesControl((MatchedValuesControl) c);
+ }
+ else
+ {
+ try
+ {
+ MatchedValuesControl matchedValuesControl =
+ MatchedValuesControl.decodeControl(c);
+ localOp.setMatchedValuesControl(matchedValuesControl);
+ }
+ catch (LDAPException le)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, le);
+ }
+
+ localOp.setResultCode(
+ ResultCode.valueOf(le.getResultCode()));
+ localOp.appendErrorMessage(le.getMessage());
+
+ break searchProcessing;
+ }
+ }
+ }
+ else if (oid.equals(OID_ACCOUNT_USABLE_CONTROL))
+ {
+ localOp.setIncludeUsableControl(true);
+ }
+ else if (oid.equals(OID_REAL_ATTRS_ONLY))
+ {
+ localOp.setRealAttributesOnly(true);
+ }
+ else if (oid.equals(OID_VIRTUAL_ATTRS_ONLY))
+ {
+ localOp.setVirtualAttributesOnly(true);
+ }
+ else if(oid.equals(OID_GET_EFFECTIVE_RIGHTS))
+ {
+ GetEffectiveRights effectiveRightsControl;
+ if (c instanceof GetEffectiveRights)
+ {
+ effectiveRightsControl = (GetEffectiveRights) c;
+ }
+ else
+ {
+ try
+ {
+ effectiveRightsControl = GetEffectiveRights.decodeControl(c);
+ }
+ catch (LDAPException le)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, le);
+ }
+
+ localOp.setResultCode(ResultCode.valueOf(le.getResultCode()));
+ localOp.appendErrorMessage(le.getMessage());
+
+ break searchProcessing;
+ }
+ }
+
+ if (!AccessControlConfigManager.getInstance()
+ .getAccessControlHandler().
+ isGetEffectiveRightsAllowed(localOp,
+ effectiveRightsControl)) {
+ localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+ int msgID =
+ MSGID_SEARCH_EFFECTIVERIGHTS_INSUFFICIENT_ACCESS_RIGHTS;
+ localOp.appendErrorMessage(getMessage(msgID,
+ String.valueOf(baseDN)));
+
+ skipPostOperation = true;
+ break searchProcessing;
+ }
+ }
+
+ // NYI -- Add support for additional controls.
+ else if (c.isCritical())
+ {
+ if ((backend == null) || (! backend.supportsControl(oid)))
+ {
+ localOp.setResultCode(
+ ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
+
+ int msgID = MSGID_SEARCH_UNSUPPORTED_CRITICAL_CONTROL;
+ localOp.appendErrorMessage(getMessage(msgID, oid));
+
+ break searchProcessing;
+ }
+ }
+ }
+ }
+
+
+ // Check to see if the client has permission to perform the
+ // search.
+
+ // FIXME: for now assume that this will check all permission
+ // pertinent to the operation. This includes proxy authorization
+ // and any other controls specified.
+ if (AccessControlConfigManager.getInstance()
+ .getAccessControlHandler().isAllowed(localOp) == false) {
+ localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+
+ int msgID = MSGID_SEARCH_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
+ localOp.appendErrorMessage(
+ getMessage(msgID, String.valueOf(baseDN)));
+
+ skipPostOperation = true;
+ break searchProcessing;
+ }
+
+ // Check for and handle a request to cancel this operation.
+ if (localOp.getCancelRequest() != null)
+ {
+ localOp.indicateCancelled(localOp.getCancelRequest());
+ localOp.setProcessingStopTime();
+ return;
+ }
+
+
+ // Invoke the pre-operation search plugins.
+ PreOperationPluginResult preOpResult =
+ pluginConfigManager.invokePreOperationSearchPlugins(localOp);
+ if (preOpResult.connectionTerminated())
+ {
+ // There's no point in continuing with anything. Log the request and
+ // result and return.
+ localOp.setResultCode(ResultCode.CANCELED);
+
+ int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
+ localOp.appendErrorMessage(getMessage(msgID));
+
+ localOp.setProcessingStopTime();
+ return;
+ }
+ else if (preOpResult.sendResponseImmediately())
+ {
+ skipPostOperation = true;
+ break searchProcessing;
+ }
+ else if (preOpResult.skipCoreProcessing())
+ {
+ skipPostOperation = false;
+ break searchProcessing;
+ }
+
+
+ // Check for and handle a request to cancel this operation.
+ if (localOp.getCancelRequest() != null)
+ {
+ localOp.indicateCancelled(localOp.getCancelRequest());
+ localOp.setProcessingStopTime();
+ return;
+ }
+
+
+ // Get the backend that should hold the search base. If there is none,
+ // then fail.
+ if (backend == null)
+ {
+ localOp.setResultCode(ResultCode.NO_SUCH_OBJECT);
+ localOp.appendErrorMessage(
+ getMessage(MSGID_SEARCH_BASE_DOESNT_EXIST,
+ String.valueOf(baseDN)));
+ break searchProcessing;
+ }
+
+
+ // We'll set the result code to "success". If a problem occurs, then it
+ // will be overwritten.
+ localOp.setResultCode(ResultCode.SUCCESS);
+
+
+ // If there's a persistent search, then register it with the server.
+ if (persistentSearch != null)
+ {
+ DirectoryServer.registerPersistentSearch(persistentSearch);
+ localOp.setSendResponse(false);
+ }
+
+
+ // Process the search in the backend and all its subordinates.
+ try
+ {
+ if (processSearch)
+ {
+ localOp.searchBackend(backend);
+ }
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResultCode(de.getResultCode());
+ localOp.appendErrorMessage(de.getErrorMessage());
+ localOp.setMatchedDN(de.getMatchedDN());
+ localOp.setReferralURLs(de.getReferralURLs());
+
+ if (persistentSearch != null)
+ {
+ DirectoryServer.deregisterPersistentSearch(persistentSearch);
+ localOp.setSendResponse(true);
+ }
+
+ break searchProcessing;
+ }
+ catch (CancelledOperationException coe)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, coe);
+ }
+
+ CancelResult cancelResult = coe.getCancelResult();
+
+ localOp.setCancelResult(cancelResult);
+ localOp.setResultCode(cancelResult.getResultCode());
+
+ String message = coe.getMessage();
+ if ((message != null) && (message.length() > 0))
+ {
+ localOp.appendErrorMessage(message);
+ }
+
+ if (persistentSearch != null)
+ {
+ DirectoryServer.deregisterPersistentSearch(persistentSearch);
+ localOp.setSendResponse(true);
+ }
+
+ skipPostOperation = true;
+ break searchProcessing;
+ }
+ catch (Exception e)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+
+ localOp.setResultCode(
+ DirectoryServer.getServerErrorResultCode());
+
+ int msgID = MSGID_SEARCH_BACKEND_EXCEPTION;
+ localOp.appendErrorMessage(
+ getMessage(msgID, getExceptionMessage(e)));
+
+ if (persistentSearch != null)
+ {
+ DirectoryServer.deregisterPersistentSearch(persistentSearch);
+ localOp.setSendResponse(true);
+ }
+
+ skipPostOperation = true;
+ break searchProcessing;
+ }
+ }
+
+
+ // Check for and handle a request to cancel this operation.
+ if (localOp.getCancelRequest() != null)
+ {
+ localOp.indicateCancelled(localOp.getCancelRequest());
+ localOp.setProcessingStopTime();
+ return;
+ }
+
+
+ // Invoke the post-operation search plugins.
+ if (! skipPostOperation)
+ {
+ PostOperationPluginResult postOperationResult =
+ pluginConfigManager.invokePostOperationSearchPlugins(localOp);
+ if (postOperationResult.connectionTerminated())
+ {
+ localOp.setResultCode(ResultCode.CANCELED);
+
+ int msgID = MSGID_CANCELED_BY_POSTOP_DISCONNECT;
+ localOp.appendErrorMessage(getMessage(msgID));
+
+ localOp.setProcessingStopTime();
+ return;
+ }
+ }
+
+ }
+
+ /**
+ * Perform a bind operation against a local backend.
+ *
+ * @param operation - The operation to perform
+ */
+ public void processBind(BindOperation operation)
+ {
+ LocalBackendBindOperation localOperation =
+ new LocalBackendBindOperation(operation);
+
+ processLocalBind(localOperation);
+ }
+
+ /**
+ * Perform a local bind operation against a local backend.
+ *
+ * @param operation - The operation to perform
+ */
+ private void processLocalBind(LocalBackendBindOperation localOp)
+ {
+ ClientConnection clientConnection = localOp.getClientConnection();
+
+ boolean returnAuthzID = false;
+ int sizeLimit = DirectoryServer.getSizeLimit();
+ int timeLimit = DirectoryServer.getTimeLimit();
+ int lookthroughLimit = DirectoryServer.getLookthroughLimit();
+ boolean skipPostOperation = false;
+
+ // The password policy state information for this bind operation.
+ PasswordPolicyState pwPolicyState = null;
+
+ // The password policy error type that should be included in the response
+ // control
+ PasswordPolicyErrorType pwPolicyErrorType = null;
+
+ // Indicates whether the client included the password policy control in the
+ // bind request.
+ boolean pwPolicyControlRequested = false;
+
+ // Indicates whether the authentication should use a grace login if it is
+ // successful.
+ boolean isGraceLogin = false;
+
+ // Indicates whether the user's password must be changed before any other
+ // operations will be allowed.
+ boolean mustChangePassword = false;
+
+ // The password policy warning type that should be included in the response
+ // control
+ PasswordPolicyWarningType pwPolicyWarningType = null;
+
+ // The password policy warning value that should be included in the response
+ // control.
+ int pwPolicyWarningValue = -1 ;
+
+ String saslMechanism = localOp.getSASLMechanism();
+
+ // Indicates whether the warning notification that should be sent to
+ // the user would be the first warning.
+ boolean isFirstWarning = false;
+
+ // The entry of the user that successfully authenticated during processing
+ // of this bind operation.
+ Entry authenticatedUserEntry = null;
+
+ // The password policy state information for this bind operation.
+ pwPolicyState = null;
+
+ // Get the plugin config manager that will be used for invoking plugins.
+ PluginConfigManager pluginConfigManager =
+ DirectoryServer.getPluginConfigManager();
+
+
+ // Create a labeled block of code that we can break out of if a problem is
+ // detected.
+bindProcessing:
+ {
+ DN bindDN = localOp.getBindDN();
+ // Check to see if the client has permission to perform the
+ // bind.
+
+ // FIXME: for now assume that this will check all permission
+ // pertinent to the operation. This includes any controls
+ // specified.
+ if (AccessControlConfigManager.getInstance()
+ .getAccessControlHandler().isAllowed(localOp) == false) {
+ localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+ int msgID = MSGID_BIND_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
+ String message = getMessage(msgID, String.valueOf(bindDN));
+ localOp.setAuthFailureReason(msgID, message);
+
+ skipPostOperation = true;
+ break bindProcessing;
+ }
+
+ // Check to see if there are any controls in the request. If so, then see
+ // if there is any special processing required.
+ List<Control> requestControls = localOp.getRequestControls();
+ if ((requestControls != null) && (! requestControls.isEmpty()))
+ {
+ for (int i=0; i < requestControls.size(); i++)
+ {
+ Control c = requestControls.get(i);
+ String oid = c.getOID();
+
+ if (oid.equals(OID_AUTHZID_REQUEST))
+ {
+ returnAuthzID = true;
+ }
+ else if (oid.equals(OID_PASSWORD_POLICY_CONTROL))
+ {
+ pwPolicyControlRequested = true;
+ }
+
+ // NYI -- Add support for additional controls.
+ else if (c.isCritical())
+ {
+ localOp.setResultCode(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
+
+ int msgID = MSGID_BIND_UNSUPPORTED_CRITICAL_CONTROL;
+ localOp.appendErrorMessage(getMessage(msgID, String.valueOf(oid)));
+
+ break bindProcessing;
+ }
+ }
+ }
+
+
+ // Check to see if this is a simple bind or a SASL bind and process
+ // accordingly.
+ switch (localOp.getAuthenticationType())
+ {
+ case SIMPLE:
+ // See if this is an anonymous bind. If so, then determine whether
+ // to allow it.
+
+ ByteString simplePassword = localOp.getSimplePassword();
+ if ((simplePassword == null) || (simplePassword.value().length == 0))
+ {
+ // If the server is in lockdown mode, then fail.
+ if (DirectoryServer.lockdownMode())
+ {
+ localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+ int msgID = MSGID_BIND_REJECTED_LOCKDOWN_MODE;
+ localOp.setAuthFailureReason(msgID, getMessage(msgID));
+
+ localOp.setProcessingStopTime();
+ logBindResponse(localOp);
+ break bindProcessing;
+ }
+
+ // If there is a bind DN, then see whether that is acceptable.
+ if (DirectoryServer.bindWithDNRequiresPassword() &&
+ ((bindDN != null) && (! bindDN.isNullDN())))
+ {
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+
+ int msgID = MSGID_BIND_DN_BUT_NO_PASSWORD;
+ String message = getMessage(msgID);
+ localOp.setAuthFailureReason(msgID, message);
+ break bindProcessing;
+ }
+
+
+ // Invoke the pre-operation bind plugins.
+ PreOperationPluginResult preOpResult =
+ pluginConfigManager.invokePreOperationBindPlugins(localOp);
+ if (preOpResult.connectionTerminated())
+ {
+ // There's no point in continuing with anything. Log the result
+ // and return.
+ localOp.setResultCode(ResultCode.CANCELED);
+
+ int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
+ localOp.appendErrorMessage(getMessage(msgID));
+
+ localOp.setProcessingStopTime();
+ return;
+ }
+ else if (preOpResult.sendResponseImmediately())
+ {
+ skipPostOperation = true;
+ break bindProcessing;
+ }
+ else if (preOpResult.skipCoreProcessing())
+ {
+ skipPostOperation = false;
+ break bindProcessing;
+ }
+
+ localOp.setResultCode(ResultCode.SUCCESS);
+ localOp.setAuthenticationInfo(new AuthenticationInfo());
+ break bindProcessing;
+ }
+
+ // See if the bind DN is actually one of the alternate root DNs
+ // defined in the server. If so, then replace it with the actual DN
+ // for that user.
+ DN actualRootDN = DirectoryServer.getActualRootBindDN(bindDN);
+ if (actualRootDN != null)
+ {
+ bindDN = actualRootDN;
+ }
+
+ // Get the user entry based on the bind DN. If it does not exist,
+ // then fail.
+ Lock userLock = null;
+ for (int i=0; i < 3; i++)
+ {
+ userLock = LockManager.lockRead(bindDN);
+ if (userLock != null)
+ {
+ break;
+ }
+ }
+
+ if (userLock == null)
+ {
+ int msgID = MSGID_BIND_OPERATION_CANNOT_LOCK_USER;
+ String message = getMessage(msgID, String.valueOf(bindDN));
+
+ localOp.setResultCode(DirectoryServer.getServerErrorResultCode());
+ localOp.setAuthFailureReason(msgID, message);
+ break bindProcessing;
+ }
+
+ try
+ {
+ Entry userEntry;
+ try
+ {
+ userEntry = backend.getEntry(bindDN);
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+ localOp.setAuthFailureReason(de.getMessageID(),
+ de.getErrorMessage());
+
+ userEntry = null;
+ break bindProcessing;
+ }
+
+ if (userEntry == null)
+ {
+
+ int msgID = MSGID_BIND_OPERATION_UNKNOWN_USER;
+ String message = getMessage(msgID, String.valueOf(bindDN));
+
+ localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+ localOp.setAuthFailureReason(msgID, message);
+ break bindProcessing;
+ }
+ else
+ {
+ localOp.setUserEntryDN(userEntry.getDN());
+ }
+
+
+ // Check to see if the user has a password. If not, then fail.
+ // FIXME -- We need to have a way to enable/disable debugging.
+ pwPolicyState = new PasswordPolicyState(userEntry, false, false);
+ AttributeType pwType
+ = pwPolicyState.getPolicy().getPasswordAttribute();
+
+ List<Attribute> pwAttr = userEntry.getAttribute(pwType);
+ if ((pwAttr == null) || (pwAttr.isEmpty()))
+ {
+ int msgID = MSGID_BIND_OPERATION_NO_PASSWORD;
+ String message = getMessage(msgID, String.valueOf(bindDN));
+
+ localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+ localOp.setAuthFailureReason(msgID, message);
+ break bindProcessing;
+ }
+
+
+ // Check to see if the authentication must be done in a secure
+ // manner. If so, then the client connection must be secure.
+ if (pwPolicyState.getPolicy().requireSecureAuthentication() &&
+ (! clientConnection.isSecure()))
+ {
+ int msgID = MSGID_BIND_OPERATION_INSECURE_SIMPLE_BIND;
+ String message = getMessage(msgID, String.valueOf(bindDN));
+
+ localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+ localOp.setAuthFailureReason(msgID, message);
+ break bindProcessing;
+ }
+
+
+ // Check to see if the user is administratively disabled or locked.
+ if (pwPolicyState.isDisabled())
+ {
+ int msgID = MSGID_BIND_OPERATION_ACCOUNT_DISABLED;
+ String message = getMessage(msgID, String.valueOf(bindDN));
+
+ localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+ localOp.setAuthFailureReason(msgID, message);
+ break bindProcessing;
+ }
+ else if (pwPolicyState.isAccountExpired())
+ {
+ int msgID = MSGID_BIND_OPERATION_ACCOUNT_EXPIRED;
+ String message = getMessage(msgID, String.valueOf(bindDN));
+
+ localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+ localOp.setAuthFailureReason(msgID, message);
+
+ pwPolicyState.generateAccountStatusNotification(
+ AccountStatusNotificationType.ACCOUNT_EXPIRED, bindDN, msgID,
+ message);
+
+ break bindProcessing;
+ }
+ else if (pwPolicyState.lockedDueToFailures())
+ {
+ int msgID = MSGID_BIND_OPERATION_ACCOUNT_FAILURE_LOCKED;
+ String message = getMessage(msgID, String.valueOf(bindDN));
+
+ if (pwPolicyErrorType == null)
+ {
+ pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
+ }
+
+ localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+ localOp.setAuthFailureReason(msgID, message);
+ break bindProcessing;
+ }
+ else if (pwPolicyState.lockedDueToMaximumResetAge())
+ {
+ int msgID = MSGID_BIND_OPERATION_ACCOUNT_RESET_LOCKED;
+ String message = getMessage(msgID, String.valueOf(bindDN));
+
+ if (pwPolicyErrorType == null)
+ {
+ pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
+ }
+
+ localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+ localOp.setAuthFailureReason(msgID, message);
+
+ pwPolicyState.generateAccountStatusNotification(
+ AccountStatusNotificationType.ACCOUNT_RESET_LOCKED, bindDN,
+ msgID, message);
+
+ break bindProcessing;
+ }
+ else if (pwPolicyState.lockedDueToIdleInterval())
+ {
+ int msgID = MSGID_BIND_OPERATION_ACCOUNT_IDLE_LOCKED;
+ String message = getMessage(msgID, String.valueOf(bindDN));
+
+ if (pwPolicyErrorType == null)
+ {
+ pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
+ }
+
+ localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+ localOp.setAuthFailureReason(msgID, message);
+
+ pwPolicyState.generateAccountStatusNotification(
+ AccountStatusNotificationType.ACCOUNT_IDLE_LOCKED, bindDN,
+ msgID, message);
+
+ break bindProcessing;
+ }
+
+
+ // Determine whether the password is expired, or whether the user
+ // should be warned about an upcoming expiration.
+ if (pwPolicyState.isPasswordExpired())
+ {
+ if (pwPolicyErrorType == null)
+ {
+ pwPolicyErrorType = PasswordPolicyErrorType.PASSWORD_EXPIRED;
+ }
+
+ int maxGraceLogins
+ = pwPolicyState.getPolicy().getGraceLoginCount();
+ if ((maxGraceLogins > 0) && pwPolicyState.mayUseGraceLogin())
+ {
+ List<Long> graceLoginTimes = pwPolicyState.getGraceLoginTimes();
+ if ((graceLoginTimes == null) ||
+ (graceLoginTimes.size() < maxGraceLogins))
+ {
+ isGraceLogin = true;
+ mustChangePassword = true;
+
+ if (pwPolicyWarningType == null)
+ {
+ pwPolicyWarningType =
+ PasswordPolicyWarningType.GRACE_LOGINS_REMAINING;
+ pwPolicyWarningValue = maxGraceLogins -
+ (graceLoginTimes.size() + 1);
+ }
+ }
+ else
+ {
+ int msgID = MSGID_BIND_OPERATION_PASSWORD_EXPIRED;
+ String message = getMessage(msgID, String.valueOf(bindDN));
+
+ localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+ localOp.setAuthFailureReason(msgID, message);
+
+ pwPolicyState.generateAccountStatusNotification(
+ AccountStatusNotificationType.PASSWORD_EXPIRED, bindDN,
+ msgID, message);
+
+ break bindProcessing;
+ }
+ }
+ else
+ {
+ int msgID = MSGID_BIND_OPERATION_PASSWORD_EXPIRED;
+ String message = getMessage(msgID, String.valueOf(bindDN));
+
+ localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+ localOp.setAuthFailureReason(msgID, message);
+
+ pwPolicyState.generateAccountStatusNotification(
+ AccountStatusNotificationType.PASSWORD_EXPIRED, bindDN,
+ msgID, message);
+
+ break bindProcessing;
+ }
+ }
+ else if (pwPolicyState.shouldWarn())
+ {
+ int numSeconds = pwPolicyState.getSecondsUntilExpiration();
+ String timeToExpiration = secondsToTimeString(numSeconds);
+
+ int msgID = MSGID_BIND_PASSWORD_EXPIRING;
+ String message = getMessage(msgID, timeToExpiration);
+ localOp.appendErrorMessage(message);
+
+ if (pwPolicyWarningType == null)
+ {
+ pwPolicyWarningType =
+ PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION;
+ pwPolicyWarningValue = numSeconds;
+ }
+
+ isFirstWarning = pwPolicyState.isFirstWarning();
+ }
+
+
+ // Check to see if the user's password has been reset.
+ if (pwPolicyState.mustChangePassword())
+ {
+ mustChangePassword = true;
+
+ if (pwPolicyErrorType == null)
+ {
+ pwPolicyErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET;
+ }
+ }
+
+
+ // Invoke the pre-operation bind plugins.
+ PreOperationPluginResult preOpResult =
+ pluginConfigManager.invokePreOperationBindPlugins(localOp);
+ if (preOpResult.connectionTerminated())
+ {
+ // There's no point in continuing with anything. Log the result
+ // and return.
+ localOp.setResultCode(ResultCode.CANCELED);
+
+ int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
+ localOp.appendErrorMessage(getMessage(msgID));
+
+ localOp.setProcessingStopTime();
+ return;
+ }
+ else if (preOpResult.sendResponseImmediately())
+ {
+ skipPostOperation = true;
+ break bindProcessing;
+ }
+ else if (preOpResult.skipCoreProcessing())
+ {
+ skipPostOperation = false;
+ break bindProcessing;
+ }
+
+
+ // Determine whether the provided password matches any of the stored
+ // passwords for the user.
+ if (pwPolicyState.passwordMatches(simplePassword))
+ {
+ localOp.setResultCode(ResultCode.SUCCESS);
+
+ boolean isRoot = DirectoryServer.isRootDN(userEntry.getDN());
+ if (DirectoryServer.lockdownMode() && (! isRoot))
+ {
+ localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+ int msgID = MSGID_BIND_REJECTED_LOCKDOWN_MODE;
+ localOp.setAuthFailureReason(msgID, getMessage(msgID));
+
+ break bindProcessing;
+ }
+ localOp.setAuthenticationInfo(new AuthenticationInfo(
+ userEntry,
+ simplePassword,
+ isRoot));
+
+
+ // See if the user's entry contains a custom size limit.
+ AttributeType attrType =
+ DirectoryServer.getAttributeType(OP_ATTR_USER_SIZE_LIMIT,
+ true);
+ List<Attribute> attrList = userEntry.getAttribute(attrType);
+ if ((attrList != null) && (attrList.size() == 1))
+ {
+ Attribute a = attrList.get(0);
+ LinkedHashSet<AttributeValue> values = a.getValues();
+ Iterator<AttributeValue> iterator = values.iterator();
+ if (iterator.hasNext())
+ {
+ AttributeValue v = iterator.next();
+ if (iterator.hasNext())
+ {
+ int msgID = MSGID_BIND_MULTIPLE_USER_SIZE_LIMITS;
+ String message =
+ getMessage(msgID, String.valueOf(userEntry.getDN()));
+ logError(ErrorLogCategory.CORE_SERVER,
+ ErrorLogSeverity.SEVERE_WARNING, message, msgID);
+ }
+ else
+ {
+ try
+ {
+ sizeLimit = Integer.parseInt(v.getStringValue());
+ }
+ catch (Exception e)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+
+ int msgID = MSGID_BIND_CANNOT_PROCESS_USER_SIZE_LIMIT;
+ String message =
+ getMessage(msgID, v.getStringValue(),
+ String.valueOf(userEntry.getDN()));
+ logError(ErrorLogCategory.CORE_SERVER,
+ ErrorLogSeverity.SEVERE_WARNING, message, msgID);
+ }
+ }
+ }
+ }
+
+
+ // See if the user's entry contains a custom time limit.
+ attrType =
+ DirectoryServer.getAttributeType(OP_ATTR_USER_TIME_LIMIT,
+ true);
+ attrList = userEntry.getAttribute(attrType);
+ if ((attrList != null) && (attrList.size() == 1))
+ {
+ Attribute a = attrList.get(0);
+ LinkedHashSet<AttributeValue> values = a.getValues();
+ Iterator<AttributeValue> iterator = values.iterator();
+ if (iterator.hasNext())
+ {
+ AttributeValue v = iterator.next();
+ if (iterator.hasNext())
+ {
+ int msgID = MSGID_BIND_MULTIPLE_USER_TIME_LIMITS;
+ String message =
+ getMessage(msgID, String.valueOf(userEntry.getDN()));
+ logError(ErrorLogCategory.CORE_SERVER,
+ ErrorLogSeverity.SEVERE_WARNING, message, msgID);
+ }
+ else
+ {
+ try
+ {
+ timeLimit = Integer.parseInt(v.getStringValue());
+ }
+ catch (Exception e)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+
+ int msgID = MSGID_BIND_CANNOT_PROCESS_USER_TIME_LIMIT;
+ String message =
+ getMessage(msgID, v.getStringValue(),
+ String.valueOf(userEntry.getDN()));
+ logError(ErrorLogCategory.CORE_SERVER,
+ ErrorLogSeverity.SEVERE_WARNING, message, msgID);
+ }
+ }
+ }
+ }
+
+
+ // See if the user's entry contains a custom lookthrough limit.
+ attrType =
+ DirectoryServer.getAttributeType(
+ OP_ATTR_USER_LOOKTHROUGH_LIMIT, true);
+ attrList = userEntry.getAttribute(attrType);
+ if ((attrList != null) && (attrList.size() == 1))
+ {
+ Attribute a = attrList.get(0);
+ LinkedHashSet<AttributeValue> values = a.getValues();
+ Iterator<AttributeValue> iterator = values.iterator();
+ if (iterator.hasNext())
+ {
+ AttributeValue v = iterator.next();
+ if (iterator.hasNext())
+ {
+ int msgID = MSGID_BIND_MULTIPLE_USER_LOOKTHROUGH_LIMITS;
+ String message =
+ getMessage(msgID, String.valueOf(userEntry.getDN()));
+ logError(ErrorLogCategory.CORE_SERVER,
+ ErrorLogSeverity.SEVERE_WARNING, message, msgID);
+ }
+ else
+ {
+ try
+ {
+ lookthroughLimit = Integer.parseInt(v.getStringValue());
+ }
+ catch (Exception e)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+
+ int msgID =
+ MSGID_BIND_CANNOT_PROCESS_USER_LOOKTHROUGH_LIMIT;
+ String message =
+ getMessage(msgID, v.getStringValue(),
+ String.valueOf(userEntry.getDN()));
+ logError(ErrorLogCategory.CORE_SERVER,
+ ErrorLogSeverity.SEVERE_WARNING, message, msgID);
+ }
+ }
+ }
+ }
+
+
+ pwPolicyState.handleDeprecatedStorageSchemes(simplePassword);
+ pwPolicyState.clearFailureLockout();
+
+ if (isFirstWarning)
+ {
+ pwPolicyState.setWarnedTime();
+
+ int numSeconds = pwPolicyState.getSecondsUntilExpiration();
+ String timeToExpiration = secondsToTimeString(numSeconds);
+
+ int msgID = MSGID_BIND_PASSWORD_EXPIRING;
+ String message = getMessage(msgID, timeToExpiration);
+
+ pwPolicyState.generateAccountStatusNotification(
+ AccountStatusNotificationType.PASSWORD_EXPIRING, bindDN,
+ msgID, message);
+ }
+
+ if (isGraceLogin)
+ {
+ pwPolicyState.updateGraceLoginTimes();
+ }
+
+ pwPolicyState.setLastLoginTime();
+ }
+ else
+ {
+ int msgID = MSGID_BIND_OPERATION_WRONG_PASSWORD;
+ String message = getMessage(msgID);
+
+ localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+ localOp.setAuthFailureReason(msgID, message);
+
+ if (pwPolicyState.getPolicy().getLockoutFailureCount() > 0)
+ {
+ pwPolicyState.updateAuthFailureTimes();
+ if (pwPolicyState.lockedDueToFailures())
+ {
+ AccountStatusNotificationType notificationType;
+
+ int lockoutDuration = pwPolicyState.getSecondsUntilUnlock();
+ if (lockoutDuration > -1)
+ {
+ notificationType = AccountStatusNotificationType.
+ ACCOUNT_TEMPORARILY_LOCKED;
+ msgID = MSGID_BIND_ACCOUNT_TEMPORARILY_LOCKED;
+ message = getMessage(msgID,
+ secondsToTimeString(lockoutDuration));
+ }
+ else
+ {
+ notificationType = AccountStatusNotificationType.
+ ACCOUNT_PERMANENTLY_LOCKED;
+ msgID = MSGID_BIND_ACCOUNT_PERMANENTLY_LOCKED;
+ message = getMessage(msgID);
+ }
+
+ pwPolicyState.generateAccountStatusNotification(
+ notificationType, localOp.getUserEntryDN(),
+ msgID, message);
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+
+ int msgID = MSGID_BIND_OPERATION_PASSWORD_VALIDATION_EXCEPTION;
+ String message = getMessage(msgID, getExceptionMessage(e));
+
+ localOp.setResultCode(DirectoryServer.getServerErrorResultCode());
+ localOp.setAuthFailureReason(msgID, message);
+ break bindProcessing;
+ }
+ finally
+ {
+ // No matter what, make sure to unlock the user's entry.
+ LockManager.unlock(bindDN, userLock);
+ }
+
+ break;
+
+
+ case SASL:
+ // Get the appropriate authentication handler for this request based
+ // on the SASL mechanism. If there is none, then fail.
+ SASLMechanismHandler saslHandler =
+ DirectoryServer.getSASLMechanismHandler(saslMechanism);
+ if (saslHandler == null)
+ {
+ localOp.setResultCode(ResultCode.AUTH_METHOD_NOT_SUPPORTED);
+
+ int msgID = MSGID_BIND_OPERATION_UNKNOWN_SASL_MECHANISM;
+ String message = getMessage(msgID, saslMechanism);
+
+ localOp.appendErrorMessage(message);
+ localOp.setAuthFailureReason(msgID, message);
+ break bindProcessing;
+ }
+
+
+ // Check to see if the client has sufficient permission to perform the
+ // bind.
+ // NYI
+
+
+ // Invoke the pre-operation bind plugins.
+ PreOperationPluginResult preOpResult =
+ pluginConfigManager.invokePreOperationBindPlugins(localOp);
+ if (preOpResult.connectionTerminated())
+ {
+ // There's no point in continuing with anything. Log the result
+ // and return.
+ localOp.setResultCode(ResultCode.CANCELED);
+
+ int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
+ localOp.appendErrorMessage(getMessage(msgID));
+
+ localOp.setProcessingStopTime();
+ return;
+ }
+ else if (preOpResult.sendResponseImmediately())
+ {
+ skipPostOperation = true;
+ break bindProcessing;
+ }
+ else if (preOpResult.skipCoreProcessing())
+ {
+ skipPostOperation = false;
+ break bindProcessing;
+ }
+
+ // Actually process the SASL bind.
+ saslHandler.processSASLBind(localOp);
+
+ // If the server is operating in lockdown mode, then we will need to
+ // ensure that the authentication was successful and performed as a
+ // root user to continue.
+ if (DirectoryServer.lockdownMode())
+ {
+ ResultCode resultCode = localOp.getResultCode();
+ if (resultCode != ResultCode.SASL_BIND_IN_PROGRESS)
+ {
+ if ((resultCode != ResultCode.SUCCESS) ||
+ (localOp.getSASLAuthUserEntry() == null) ||
+ (! DirectoryServer.isRootDN(
+ localOp.getSASLAuthUserEntry().getDN())))
+ {
+ localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+ int msgID = MSGID_BIND_REJECTED_LOCKDOWN_MODE;
+ localOp.setAuthFailureReason(msgID, getMessage(msgID));
+
+ break bindProcessing;
+ }
+ }
+ }
+
+ // Create the password policy state object.
+ String userDNString;
+ Entry saslAuthUserEntry = localOp.getSASLAuthUserEntry();
+ if (saslAuthUserEntry == null)
+ {
+ pwPolicyState = null;
+ userDNString = null;
+ }
+ else
+ {
+ try
+ {
+ // FIXME -- Need to have a way to enable debugging.
+ pwPolicyState = new PasswordPolicyState(saslAuthUserEntry, false,
+ false);
+ localOp.setUserEntryDN(saslAuthUserEntry.getDN());
+ userDNString = String.valueOf(localOp.getUserEntryDN());
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResponseData(de);
+ break bindProcessing;
+ }
+ }
+
+
+ // Perform password policy checks that will need to be completed
+ // regardless of whether the authentication was successful.
+ if (pwPolicyState != null)
+ {
+ if (pwPolicyState.isDisabled())
+ {
+ localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+ int msgID = MSGID_BIND_OPERATION_ACCOUNT_DISABLED;
+ String message = getMessage(msgID, userDNString);
+ localOp.setAuthFailureReason(msgID, message);
+ break bindProcessing;
+ }
+ else if (pwPolicyState.isAccountExpired())
+ {
+ localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+ int msgID = MSGID_BIND_OPERATION_ACCOUNT_EXPIRED;
+ String message = getMessage(msgID, userDNString);
+ localOp.setAuthFailureReason(msgID, message);
+
+ pwPolicyState.generateAccountStatusNotification(
+ AccountStatusNotificationType.ACCOUNT_EXPIRED, bindDN, msgID,
+ message);
+
+ break bindProcessing;
+ }
+
+ if (pwPolicyState.getPolicy().requireSecureAuthentication() &&
+ (! clientConnection.isSecure()) &&
+ (! saslHandler.isSecure(saslMechanism)))
+ {
+ localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+ int msgID = MSGID_BIND_OPERATION_INSECURE_SASL_BIND;
+ String message = getMessage(msgID, saslMechanism, userDNString);
+ localOp.setAuthFailureReason(msgID, message);
+ break bindProcessing;
+ }
+
+ if (pwPolicyState.lockedDueToFailures())
+ {
+ localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+ if (pwPolicyErrorType == null)
+ {
+ pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
+ }
+
+ int msgID = MSGID_BIND_OPERATION_ACCOUNT_FAILURE_LOCKED;
+ String message = getMessage(msgID, userDNString);
+ localOp.setAuthFailureReason(msgID, message);
+ break bindProcessing;
+ }
+
+ if (pwPolicyState.lockedDueToIdleInterval())
+ {
+ localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+ if (pwPolicyErrorType == null)
+ {
+ pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
+ }
+
+ int msgID = MSGID_BIND_OPERATION_ACCOUNT_IDLE_LOCKED;
+ String message = getMessage(msgID, userDNString);
+ localOp.setAuthFailureReason(msgID, message);
+
+ pwPolicyState.generateAccountStatusNotification(
+ AccountStatusNotificationType.ACCOUNT_IDLE_LOCKED, bindDN,
+ msgID, message);
+
+ break bindProcessing;
+ }
+
+
+ if (saslHandler.isPasswordBased(saslMechanism))
+ {
+ if (pwPolicyState.lockedDueToMaximumResetAge())
+ {
+ localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+
+ if (pwPolicyErrorType == null)
+ {
+ pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
+ }
+
+ int msgID = MSGID_BIND_OPERATION_ACCOUNT_RESET_LOCKED;
+ String message = getMessage(msgID, userDNString);
+ localOp.setAuthFailureReason(msgID, message);
+
+ pwPolicyState.generateAccountStatusNotification(
+ AccountStatusNotificationType.ACCOUNT_RESET_LOCKED, bindDN,
+ msgID, message);
+
+ break bindProcessing;
+ }
+
+ if (pwPolicyState.isPasswordExpired())
+ {
+ if (pwPolicyErrorType == null)
+ {
+ pwPolicyErrorType = PasswordPolicyErrorType.PASSWORD_EXPIRED;
+ }
+
+ int maxGraceLogins
+ = pwPolicyState.getPolicy().getGraceLoginCount();
+ if ((maxGraceLogins > 0) && pwPolicyState.mayUseGraceLogin())
+ {
+ List<Long> graceLoginTimes =
+ pwPolicyState.getGraceLoginTimes();
+ if ((graceLoginTimes == null) ||
+ (graceLoginTimes.size() < maxGraceLogins))
+ {
+ isGraceLogin = true;
+ mustChangePassword = true;
+
+ if (pwPolicyWarningType == null)
+ {
+ pwPolicyWarningType =
+ PasswordPolicyWarningType.GRACE_LOGINS_REMAINING;
+ pwPolicyWarningValue =
+ maxGraceLogins - (graceLoginTimes.size() + 1);
+ }
+ }
+ else
+ {
+ int msgID = MSGID_BIND_OPERATION_PASSWORD_EXPIRED;
+ String message = getMessage(msgID, String.valueOf(bindDN));
+
+ localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+ localOp.setAuthFailureReason(msgID, message);
+
+ pwPolicyState.generateAccountStatusNotification(
+ AccountStatusNotificationType.PASSWORD_EXPIRED, bindDN,
+ msgID, message);
+
+ break bindProcessing;
+ }
+ }
+ else
+ {
+ int msgID = MSGID_BIND_OPERATION_PASSWORD_EXPIRED;
+ String message = getMessage(msgID, String.valueOf(bindDN));
+
+ localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+ localOp.setAuthFailureReason(msgID, message);
+
+ pwPolicyState.generateAccountStatusNotification(
+ AccountStatusNotificationType.PASSWORD_EXPIRED, bindDN,
+ msgID, message);
+
+ break bindProcessing;
+ }
+ }
+ else if (pwPolicyState.shouldWarn())
+ {
+ int numSeconds = pwPolicyState.getSecondsUntilExpiration();
+ String timeToExpiration = secondsToTimeString(numSeconds);
+
+ int msgID = MSGID_BIND_PASSWORD_EXPIRING;
+ String message = getMessage(msgID, timeToExpiration);
+ localOp.appendErrorMessage(message);
+
+ if (pwPolicyWarningType == null)
+ {
+ pwPolicyWarningType =
+ PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION;
+ pwPolicyWarningValue = numSeconds;
+ }
+
+ isFirstWarning = pwPolicyState.isFirstWarning();
+ }
+ }
+ }
+
+
+ // Determine whether the authentication was successful and perform
+ // any remaining password policy processing accordingly. Also check
+ // for a custom size/time limit.
+ ResultCode resultCode = localOp.getResultCode();
+ if (resultCode == ResultCode.SUCCESS)
+ {
+ if (pwPolicyState != null)
+ {
+ if (saslHandler.isPasswordBased(saslMechanism) &&
+ pwPolicyState.mustChangePassword())
+ {
+ mustChangePassword = true;
+ }
+
+ if (isFirstWarning)
+ {
+ pwPolicyState.setWarnedTime();
+
+ int numSeconds = pwPolicyState.getSecondsUntilExpiration();
+ String timeToExpiration = secondsToTimeString(numSeconds);
+
+ int msgID = MSGID_BIND_PASSWORD_EXPIRING;
+ String message = getMessage(msgID, timeToExpiration);
+
+ pwPolicyState.generateAccountStatusNotification(
+ AccountStatusNotificationType.PASSWORD_EXPIRING, bindDN,
+ msgID, message);
+ }
+
+ if (isGraceLogin)
+ {
+ pwPolicyState.updateGraceLoginTimes();
+ }
+
+ pwPolicyState.setLastLoginTime();
+
+
+ // See if the user's entry contains a custom size limit.
+ AttributeType attrType =
+ DirectoryServer.getAttributeType(OP_ATTR_USER_SIZE_LIMIT,
+ true);
+ List<Attribute> attrList =
+ saslAuthUserEntry.getAttribute(attrType);
+ if ((attrList != null) && (attrList.size() == 1))
+ {
+ Attribute a = attrList.get(0);
+ LinkedHashSet<AttributeValue> values = a.getValues();
+ Iterator<AttributeValue> iterator = values.iterator();
+ if (iterator.hasNext())
+ {
+ AttributeValue v = iterator.next();
+ if (iterator.hasNext())
+ {
+ int msgID = MSGID_BIND_MULTIPLE_USER_SIZE_LIMITS;
+ String message = getMessage(msgID, userDNString);
+ logError(ErrorLogCategory.CORE_SERVER,
+ ErrorLogSeverity.SEVERE_WARNING, message, msgID);
+ }
+ else
+ {
+ try
+ {
+ sizeLimit = Integer.parseInt(v.getStringValue());
+ }
+ catch (Exception e)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+
+ int msgID = MSGID_BIND_CANNOT_PROCESS_USER_SIZE_LIMIT;
+ String message =
+ getMessage(msgID, v.getStringValue(), userDNString);
+ logError(ErrorLogCategory.CORE_SERVER,
+ ErrorLogSeverity.SEVERE_WARNING, message, msgID);
+ }
+ }
+ }
+ }
+
+
+ // See if the user's entry contains a custom time limit.
+ attrType =
+ DirectoryServer.getAttributeType(OP_ATTR_USER_TIME_LIMIT,
+ true);
+ attrList = saslAuthUserEntry.getAttribute(attrType);
+ if ((attrList != null) && (attrList.size() == 1))
+ {
+ Attribute a = attrList.get(0);
+ LinkedHashSet<AttributeValue> values = a.getValues();
+ Iterator<AttributeValue> iterator = values.iterator();
+ if (iterator.hasNext())
+ {
+ AttributeValue v = iterator.next();
+ if (iterator.hasNext())
+ {
+ int msgID = MSGID_BIND_MULTIPLE_USER_TIME_LIMITS;
+ String message = getMessage(msgID, userDNString);
+ logError(ErrorLogCategory.CORE_SERVER,
+ ErrorLogSeverity.SEVERE_WARNING, message, msgID);
+ }
+ else
+ {
+ try
+ {
+ timeLimit = Integer.parseInt(v.getStringValue());
+ }
+ catch (Exception e)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+
+ int msgID = MSGID_BIND_CANNOT_PROCESS_USER_TIME_LIMIT;
+ String message =
+ getMessage(msgID, v.getStringValue(), userDNString);
+ logError(ErrorLogCategory.CORE_SERVER,
+ ErrorLogSeverity.SEVERE_WARNING, message, msgID);
+ }
+ }
+ }
+ }
+
+
+ // See if the user's entry contains a custom lookthrough limit.
+ attrType =
+ DirectoryServer.getAttributeType(
+ OP_ATTR_USER_LOOKTHROUGH_LIMIT, true);
+ attrList = saslAuthUserEntry.getAttribute(attrType);
+ if ((attrList != null) && (attrList.size() == 1))
+ {
+ Attribute a = attrList.get(0);
+ LinkedHashSet<AttributeValue> values = a.getValues();
+ Iterator<AttributeValue> iterator = values.iterator();
+ if (iterator.hasNext())
+ {
+ AttributeValue v = iterator.next();
+ if (iterator.hasNext())
+ {
+ int msgID = MSGID_BIND_MULTIPLE_USER_LOOKTHROUGH_LIMITS;
+ String message = getMessage(msgID, userDNString);
+ logError(ErrorLogCategory.CORE_SERVER,
+ ErrorLogSeverity.SEVERE_WARNING, message, msgID);
+ }
+ else
+ {
+ try
+ {
+ lookthroughLimit = Integer.parseInt(v.getStringValue());
+ }
+ catch (Exception e)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+
+ int msgID =
+ MSGID_BIND_CANNOT_PROCESS_USER_LOOKTHROUGH_LIMIT;
+ String message =
+ getMessage(msgID, v.getStringValue(), userDNString);
+ logError(ErrorLogCategory.CORE_SERVER,
+ ErrorLogSeverity.SEVERE_WARNING, message, msgID);
+ }
+ }
+ }
+ }
+ }
+ }
+ else if (resultCode == ResultCode.SASL_BIND_IN_PROGRESS)
+ {
+ // FIXME -- Is any special processing needed here?
+ }
+ else
+ {
+ if (pwPolicyState != null)
+ {
+ if (saslHandler.isPasswordBased(saslMechanism))
+ {
+
+ if (pwPolicyState.getPolicy().getLockoutFailureCount() > 0)
+ {
+ pwPolicyState.updateAuthFailureTimes();
+ if (pwPolicyState.lockedDueToFailures())
+ {
+ AccountStatusNotificationType notificationType;
+ int msgID;
+ String message;
+
+ int lockoutDuration = pwPolicyState.getSecondsUntilUnlock();
+ if (lockoutDuration > -1)
+ {
+ notificationType = AccountStatusNotificationType.
+ ACCOUNT_TEMPORARILY_LOCKED;
+ msgID = MSGID_BIND_ACCOUNT_TEMPORARILY_LOCKED;
+ message = getMessage(msgID,
+ secondsToTimeString(lockoutDuration));
+ }
+ else
+ {
+ notificationType = AccountStatusNotificationType.
+ ACCOUNT_PERMANENTLY_LOCKED;
+ msgID = MSGID_BIND_ACCOUNT_PERMANENTLY_LOCKED;
+ message = getMessage(msgID);
+ }
+
+ pwPolicyState.generateAccountStatusNotification(
+ notificationType, localOp.getUserEntryDN(),
+ msgID, message);
+ }
+ }
+ }
+ }
+ }
+
+ break;
+
+
+ default:
+ // Send a protocol error response to the client and disconnect.
+ // NYI
+ return;
+ }
+ }
+
+
+ // Update the user's account with any password policy changes that may be
+ // required.
+ try
+ {
+ if (pwPolicyState != null)
+ {
+ pwPolicyState.updateUserEntry();
+ }
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResponseData(de);
+ }
+
+
+ // Invoke the post-operation bind plugins.
+ if (! skipPostOperation)
+ {
+ PostOperationPluginResult postOpResult =
+ pluginConfigManager.invokePostOperationBindPlugins(localOp);
+ if (postOpResult.connectionTerminated())
+ {
+ // There's no point in continuing with anything. Log the result
+ // and return.
+ localOp.setResultCode(ResultCode.CANCELED);
+
+ int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
+ localOp.appendErrorMessage(getMessage(msgID));
+
+ localOp.setProcessingStopTime();
+ return;
+ }
+ }
+
+
+ // Update the authentication information for the user.
+ AuthenticationInfo authInfo = localOp.getAuthenticationInfo();
+ if ((localOp.getResultCode() == ResultCode.SUCCESS) &&
+ (authInfo != null))
+ {
+ authenticatedUserEntry = authInfo.getAuthenticationEntry();
+ clientConnection.setAuthenticationInfo(authInfo);
+ clientConnection.setSizeLimit(sizeLimit);
+ clientConnection.setTimeLimit(timeLimit);
+ clientConnection.setLookthroughLimit(lookthroughLimit);
+ clientConnection.setMustChangePassword(mustChangePassword);
+
+ if (returnAuthzID)
+ {
+ localOp.addResponseControl(
+ new AuthorizationIdentityResponseControl(
+ authInfo.getAuthorizationDN()));
+ }
+ }
+
+
+ // See if we need to send a password policy control to the client. If so,
+ // then add it to the response.
+ if (localOp.getResultCode() == ResultCode.SUCCESS)
+ {
+ if (pwPolicyControlRequested)
+ {
+ PasswordPolicyResponseControl pwpControl =
+ new PasswordPolicyResponseControl(pwPolicyWarningType,
+ pwPolicyWarningValue,
+ pwPolicyErrorType);
+ localOp.addResponseControl(pwpControl);
+ }
+ else
+ {
+ if (pwPolicyErrorType == PasswordPolicyErrorType.PASSWORD_EXPIRED)
+ {
+ localOp.addResponseControl(new PasswordExpiredControl());
+ }
+ else if (pwPolicyWarningType ==
+ PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION)
+ {
+ localOp.addResponseControl(new PasswordExpiringControl(
+ pwPolicyWarningValue));
+ }
+ }
+ }
+ else
+ {
+ if (pwPolicyControlRequested)
+ {
+ PasswordPolicyResponseControl pwpControl =
+ new PasswordPolicyResponseControl(pwPolicyWarningType,
+ pwPolicyWarningValue,
+ pwPolicyErrorType);
+ localOp.addResponseControl(pwpControl);
+ }
+ else
+ {
+ if (pwPolicyErrorType == PasswordPolicyErrorType.PASSWORD_EXPIRED)
+ {
+ localOp.addResponseControl(new PasswordExpiredControl());
+ }
+ }
+ }
+
+ // Stop the processing timer.
+ localOp.setProcessingStopTime();
+
+ }
+
+ /**
+ * Perform an add operation against a local backend.
+ *
+ * @param operation - The operation to perform
+ */
+ public void processAdd(AddOperation operation)
+ {
+ LocalBackendAddOperation localOperation =
+ new LocalBackendAddOperation(operation);
+
+ processLocalAdd(localOperation);
+ }
+
+ /**
+ * Perform a local add operation against a local backend.
+ *
+ * @param operation - The operation to perform
+ */
+ private void processLocalAdd(LocalBackendAddOperation localOp)
+ {
+ ClientConnection clientConnection = localOp.getClientConnection();
+
+ // Get the plugin config manager that will be used for invoking plugins.
+ PluginConfigManager pluginConfigManager =
+ DirectoryServer.getPluginConfigManager();
+ boolean skipPostOperation = false;
+
+ // Check for and handle a request to cancel this operation.
+ if (localOp.getCancelRequest() != null)
+ {
+ localOp.indicateCancelled(localOp.getCancelRequest());
+ localOp.setProcessingStopTime();
+ 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.
+ DN entryDN = localOp.getEntryDN();
+ if (entryDN == null){
+ break addProcessing;
+ }
+
+ Map<ObjectClass, String> objectClasses =
+ localOp.getObjectClasses();
+ Map<AttributeType, List<Attribute>> userAttributes =
+ localOp.getUserAttributes();
+ Map<AttributeType, List<Attribute>> operationalAttributes =
+ localOp.getOperationalAttributes();
+
+ if ((objectClasses == null ) ||
+ (userAttributes == null) ||
+ (operationalAttributes == null)){
+ break addProcessing;
+ }
+
+ // Check for and handle a request to cancel this operation.
+ if (localOp.getCancelRequest() != null)
+ {
+ localOp.indicateCancelled(localOp.getCancelRequest());
+ localOp.setProcessingStopTime();
+ 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();
+ 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.
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+ localOp.appendErrorMessage(getMessage(MSGID_ADD_CANNOT_ADD_ROOT_DSE));
+ break addProcessing;
+ }
+ else
+ {
+ // The entry doesn't have a parent but isn't a suffix. This is not
+ // allowed.
+ localOp.setResultCode(ResultCode.NO_SUCH_OBJECT);
+ localOp.appendErrorMessage(getMessage(MSGID_ADD_ENTRY_NOT_SUFFIX,
+ String.valueOf(entryDN)));
+ break addProcessing;
+ }
+ }
+ else
+ {
+ for (int i=0; i < 3; i++)
+ {
+ parentLock = LockManager.lockRead(parentDN);
+ if (parentLock != null)
+ {
+ break;
+ }
+ }
+
+ if (parentLock == null)
+ {
+ localOp.setResultCode(DirectoryServer.getServerErrorResultCode());
+ localOp.appendErrorMessage(getMessage(MSGID_ADD_CANNOT_LOCK_PARENT,
+ String.valueOf(entryDN),
+ String.valueOf(parentDN)));
+
+ skipPostOperation = true;
+ break addProcessing;
+ }
+ }
+
+
+ try
+ {
+ // Check for and handle a request to cancel this operation.
+ if (localOp.getCancelRequest() != null)
+ {
+ localOp.indicateCancelled(localOp.getCancelRequest());
+ localOp.setProcessingStopTime();
+ 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)
+ {
+ localOp.setResultCode(DirectoryServer.getServerErrorResultCode());
+ localOp.appendErrorMessage(getMessage(MSGID_ADD_CANNOT_LOCK_ENTRY,
+ 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(localOp);
+ if (! result.continueOperationProcessing())
+ {
+ break addProcessing;
+ }
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ logError(ErrorLogCategory.SYNCHRONIZATION,
+ ErrorLogSeverity.SEVERE_ERROR,
+ MSGID_ADD_SYNCH_CONFLICT_RESOLUTION_FAILED,
+ localOp.getConnectionID(),
+ localOp.getOperationID(),
+ getExceptionMessage(de));
+
+ localOp.setResponseData(de);
+ 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))
+ {
+ localOp.setResultCode(ResultCode.ENTRY_ALREADY_EXISTS);
+ localOp.appendErrorMessage(getMessage(
+ MSGID_ADD_ENTRY_ALREADY_EXISTS,
+ String.valueOf(entryDN)));
+ break addProcessing;
+ }
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResultCode(de.getResultCode());
+ localOp.appendErrorMessage(de.getErrorMessage());
+ localOp.setMatchedDN(de.getMatchedDN());
+ localOp.setReferralURLs(de.getReferralURLs());
+ 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))
+ {
+ localOp.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.
+ localOp.setResultCode(ResultCode.NO_SUCH_OBJECT);
+ localOp.appendErrorMessage(getMessage(MSGID_ADD_NO_PARENT,
+ String.valueOf(entryDN),
+ String.valueOf(parentDN)));
+ break addProcessing;
+ }
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResultCode(de.getResultCode());
+ localOp.appendErrorMessage(de.getErrorMessage());
+ localOp.setMatchedDN(de.getMatchedDN());
+ localOp.setReferralURLs(de.getReferralURLs());
+ 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.
+ 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 (localOp.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
+ {
+ localOp.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+
+ int msgID = MSGID_ADD_MISSING_RDN_ATTRIBUTE;
+ localOp.appendErrorMessage(getMessage(msgID,
+ String.valueOf(entryDN),
+ n));
+
+ break addProcessing;
+ }
+ }
+ 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 (localOp.isSynchronizationOperation() ||
+ DirectoryServer.addMissingRDNAttributes())
+ {
+ LinkedHashSet<AttributeValue> valueList =
+ new LinkedHashSet<AttributeValue>(1);
+ valueList.add(v);
+ attrList.add(new Attribute(t, n, valueList));
+ }
+ else
+ {
+ localOp.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+
+ int msgID = MSGID_ADD_MISSING_RDN_ATTRIBUTE;
+ localOp.appendErrorMessage(
+ getMessage(msgID, String.valueOf(entryDN), n));
+
+ break addProcessing;
+ }
+ }
+ }
+ }
+ else
+ {
+ List<Attribute> attrList = userAttributes.get(t);
+ if (attrList == null)
+ {
+ if (localOp.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
+ {
+ localOp.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+
+ int msgID = MSGID_ADD_MISSING_RDN_ATTRIBUTE;
+ localOp.appendErrorMessage(
+ getMessage(msgID, String.valueOf(entryDN),n));
+
+ break addProcessing;
+ }
+ }
+ 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 (localOp.isSynchronizationOperation() ||
+ DirectoryServer.addMissingRDNAttributes())
+ {
+ LinkedHashSet<AttributeValue> valueList =
+ new LinkedHashSet<AttributeValue>(1);
+ valueList.add(v);
+ attrList.add(new Attribute(t, n, valueList));
+ }
+ else
+ {
+ localOp.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+
+ int msgID = MSGID_ADD_MISSING_RDN_ATTRIBUTE;
+ localOp.appendErrorMessage(
+ getMessage(msgID, String.valueOf(entryDN),n));
+
+ 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)
+ {
+ localOp.addObjectClassChain(oc);
+ }
+ }
+
+
+ // Create an entry object to encapsulate the set of attributes and
+ // objectclasses.
+ Entry entry = new Entry(entryDN, objectClasses, userAttributes,
+ operationalAttributes);
+ localOp.setEntryToAdd(entry);
+
+ // 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,
+ localOp)))
+ {
+ int msgID = MSGID_ADD_CHANGE_PRIVILEGE_INSUFFICIENT_PRIVILEGES;
+ localOp.appendErrorMessage(getMessage(msgID));
+ localOp.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 (! localOp.isSynchronizationOperation())
+ {
+ // FIXME -- We need to check to see if the password policy subentry
+ // might be specified virtually rather than as a real
+ // attribute.
+ PasswordPolicy pwPolicy = 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);
+ }
+
+ int msgID = MSGID_ADD_INVALID_PWPOLICY_DN_SYNTAX;
+ localOp.appendErrorMessage(
+ getMessage(msgID, String.valueOf(entryDN),
+ de.getErrorMessage()));
+
+ localOp.setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
+ break addProcessing;
+ }
+
+ pwPolicy = DirectoryServer.getPasswordPolicy(policyDN);
+ if (pwPolicy == null)
+ {
+ int msgID = MSGID_ADD_NO_SUCH_PWPOLICY;
+ localOp.appendErrorMessage(
+ getMessage(msgID, String.valueOf(entryDN),
+ String.valueOf(policyDN)));
+
+ localOp.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+ break addProcessing;
+ }
+ }
+ }
+
+ if (pwPolicy == null)
+ {
+ pwPolicy = DirectoryServer.getDefaultPasswordPolicy();
+ }
+
+ try
+ {
+ localOp.handlePasswordPolicy(pwPolicy, entry);
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResponseData(de);
+ break addProcessing;
+ }
+ }
+
+
+ // 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())
+ {
+ StringBuilder invalidReason = new StringBuilder();
+ if (! entry.conformsToSchema(parentEntry, true, true, true,
+ invalidReason))
+ {
+ localOp.setResultCode(ResultCode.OBJECTCLASS_VIOLATION);
+ localOp.setErrorMessage(invalidReason);
+ break addProcessing;
+ }
+ else
+ {
+ switch (DirectoryServer.getSyntaxEnforcementPolicy())
+ {
+ case REJECT:
+ invalidReason = new StringBuilder();
+ 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))
+ {
+ String message =
+ getMessage(MSGID_ADD_OP_INVALID_SYNTAX,
+ String.valueOf(entryDN),
+ String.valueOf(v.getStringValue()),
+ String.valueOf(a.getName()),
+ String.valueOf(invalidReason));
+ invalidReason = new StringBuilder(message);
+
+ localOp.setResultCode(
+ ResultCode.INVALID_ATTRIBUTE_SYNTAX);
+ localOp.setErrorMessage(invalidReason);
+ break addProcessing;
+ }
+ }
+ }
+ }
+ }
+
+ 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))
+ {
+ String message =
+ getMessage(MSGID_ADD_OP_INVALID_SYNTAX,
+ String.valueOf(entryDN),
+ String.valueOf(v.getStringValue()),
+ String.valueOf(a.getName()),
+ String.valueOf(invalidReason));
+ invalidReason = new StringBuilder(message);
+
+ localOp.setResultCode(
+ ResultCode.INVALID_ATTRIBUTE_SYNTAX);
+ localOp.setErrorMessage(invalidReason);
+ break addProcessing;
+ }
+ }
+ }
+ }
+ }
+
+ break;
+
+
+ case WARN:
+ invalidReason = new StringBuilder();
+ 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(ErrorLogCategory.SCHEMA,
+ ErrorLogSeverity.SEVERE_WARNING,
+ MSGID_ADD_OP_INVALID_SYNTAX,
+ 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(ErrorLogCategory.SCHEMA,
+ ErrorLogSeverity.SEVERE_WARNING,
+ MSGID_ADD_OP_INVALID_SYNTAX,
+ 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())
+ {
+ int msgID = MSGID_ADD_ATTR_IS_OBSOLETE;
+ String message = getMessage(msgID, String.valueOf(entryDN),
+ at.getNameOrOID());
+ localOp.appendErrorMessage(message);
+ localOp.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+ break addProcessing;
+ }
+ }
+
+ for (AttributeType at : operationalAttributes.keySet())
+ {
+ if (at.isObsolete())
+ {
+ int msgID = MSGID_ADD_ATTR_IS_OBSOLETE;
+ String message = getMessage(msgID, String.valueOf(entryDN),
+ at.getNameOrOID());
+ localOp.appendErrorMessage(message);
+ localOp.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+ break addProcessing;
+ }
+ }
+
+ for (ObjectClass oc : objectClasses.keySet())
+ {
+ if (oc.isObsolete())
+ {
+ int msgID = MSGID_ADD_OC_IS_OBSOLETE;
+ String message = getMessage(msgID, String.valueOf(entryDN),
+ oc.getNameOrOID());
+ localOp.appendErrorMessage(message);
+ localOp.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+ break addProcessing;
+ }
+ }
+ }
+
+ // Check to see if there are any controls in the request. If so,
+ // then
+ // see if there is any special processing required.
+ boolean noOp = false;
+ LDAPPostReadRequestControl postReadRequest = null;
+ List<Control> requestControls = localOp.getRequestControls();
+ if ((requestControls != null) && (! requestControls.isEmpty()))
+ {
+ for (int i=0; i < requestControls.size(); i++)
+ {
+ Control c = requestControls.get(i);
+ String oid = c.getOID();
+
+ 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);
+ }
+
+ localOp.setResultCode(ResultCode.valueOf(le.getResultCode()));
+ localOp.appendErrorMessage(le.getMessage());
+
+ break addProcessing;
+ }
+ }
+
+ try
+ {
+ // FIXME -- We need to determine whether the current user has
+ // permission to make this determination.
+ SearchFilter filter = assertControl.getSearchFilter();
+ if (! filter.matchesEntry(entry))
+ {
+ localOp.setResultCode(ResultCode.ASSERTION_FAILED);
+
+ localOp.appendErrorMessage(getMessage(
+ MSGID_ADD_ASSERTION_FAILED,
+ String.valueOf(entryDN)));
+
+ break addProcessing;
+ }
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResultCode(ResultCode.PROTOCOL_ERROR);
+
+ int msgID = MSGID_ADD_CANNOT_PROCESS_ASSERTION_FILTER;
+ localOp.appendErrorMessage(getMessage(msgID,
+ String.valueOf(entryDN),
+ de.getErrorMessage()));
+
+ break addProcessing;
+ }
+ }
+ else if (oid.equals(OID_LDAP_NOOP_OPENLDAP_ASSIGNED))
+ {
+ noOp = true;
+ }
+ else if (oid.equals(OID_LDAP_READENTRY_POSTREAD))
+ {
+ if (c instanceof LDAPAssertionRequestControl)
+ {
+ postReadRequest = (LDAPPostReadRequestControl) c;
+ }
+ else
+ {
+ try
+ {
+ postReadRequest = LDAPPostReadRequestControl.decodeControl(c);
+ requestControls.set(i, postReadRequest);
+ }
+ catch (LDAPException le)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, le);
+ }
+
+ localOp.setResultCode(ResultCode.valueOf(le.getResultCode()));
+ localOp.appendErrorMessage(le.getMessage());
+
+ break addProcessing;
+ }
+ }
+ }
+ 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,
+ localOp))
+ {
+ int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
+ localOp.appendErrorMessage(getMessage(msgID));
+ localOp.setResultCode(ResultCode.AUTHORIZATION_DENIED);
+ break addProcessing;
+ }
+
+
+ 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);
+ }
+
+ localOp.setResultCode(ResultCode.valueOf(le.getResultCode()));
+ localOp.appendErrorMessage(le.getMessage());
+
+ break addProcessing;
+ }
+ }
+
+
+ Entry authorizationEntry;
+ try
+ {
+ authorizationEntry = proxyControl.getAuthorizationEntry();
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResultCode(de.getResultCode());
+ localOp.appendErrorMessage(de.getErrorMessage());
+
+ break addProcessing;
+ }
+
+ if (AccessControlConfigManager.getInstance()
+ .getAccessControlHandler().isProxiedAuthAllowed(localOp,
+ authorizationEntry) == false) {
+ localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+
+ int msgID = MSGID_ADD_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
+ localOp.appendErrorMessage(getMessage(msgID,
+ String.valueOf(entryDN)));
+
+ skipPostOperation = true;
+ break addProcessing;
+ }
+ localOp.setAuthorizationEntry(authorizationEntry);
+ if (authorizationEntry == null)
+ {
+ localOp.setProxiedAuthorizationDN(DN.nullDN());
+ }
+ else
+ {
+ localOp.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,
+ localOp))
+ {
+ int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
+ localOp.appendErrorMessage(getMessage(msgID));
+ localOp.setResultCode(ResultCode.AUTHORIZATION_DENIED);
+ break addProcessing;
+ }
+
+
+ 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);
+ }
+
+ localOp.setResultCode(ResultCode.valueOf(le.getResultCode()));
+ localOp.appendErrorMessage(le.getMessage());
+
+ break addProcessing;
+ }
+ }
+
+
+ Entry authorizationEntry;
+ try
+ {
+ authorizationEntry = proxyControl.getAuthorizationEntry();
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResultCode(de.getResultCode());
+ localOp.appendErrorMessage(de.getErrorMessage());
+
+ break addProcessing;
+ }
+
+ if (AccessControlConfigManager.getInstance()
+ .getAccessControlHandler().isProxiedAuthAllowed(localOp,
+ authorizationEntry) == false) {
+ localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+
+ int msgID = MSGID_ADD_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
+ localOp.appendErrorMessage(getMessage(msgID,
+ String.valueOf(entryDN)));
+
+ skipPostOperation = true;
+ break addProcessing;
+ }
+ localOp.setAuthorizationEntry(authorizationEntry);
+ if (authorizationEntry == null)
+ {
+ localOp.setProxiedAuthorizationDN(DN.nullDN());
+ }
+ else
+ {
+ localOp.setProxiedAuthorizationDN(authorizationEntry.getDN());
+ }
+ }
+
+ // NYI -- Add support for additional controls.
+ else if (c.isCritical())
+ {
+ if ((backend == null) || (! backend.supportsControl(oid)))
+ {
+ localOp.setResultCode(
+ ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
+
+ int msgID = MSGID_ADD_UNSUPPORTED_CRITICAL_CONTROL;
+ localOp.appendErrorMessage(getMessage(msgID,
+ String.valueOf(entryDN),
+ oid));
+
+ 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(localOp) == false) {
+ localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+
+ int msgID = MSGID_ADD_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
+ localOp.appendErrorMessage(getMessage(msgID,
+ String.valueOf(entryDN)));
+
+ skipPostOperation = true;
+ break addProcessing;
+ }
+
+ // Check for and handle a request to cancel this operation.
+ if (localOp.getCancelRequest() != null)
+ {
+ localOp.indicateCancelled(localOp.getCancelRequest());
+ localOp.setProcessingStopTime();
+ return;
+ }
+
+
+ // If the operation is not a synchronization operation,
+ // Invoke the pre-operation modify plugins.
+ if (!localOp.isSynchronizationOperation())
+ {
+ PreOperationPluginResult preOpResult =
+ pluginConfigManager.invokePreOperationAddPlugins(localOp);
+ if (preOpResult.connectionTerminated())
+ {
+ // There's no point in continuing with anything. Log the result
+ // and return.
+ localOp.setResultCode(ResultCode.CANCELED);
+
+ int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
+ localOp.appendErrorMessage(getMessage(msgID));
+
+ localOp.setProcessingStopTime();
+ return;
+ }
+ else if (preOpResult.sendResponseImmediately())
+ {
+ skipPostOperation = true;
+ break addProcessing;
+ }
+ else if (preOpResult.skipCoreProcessing())
+ {
+ skipPostOperation = false;
+ break addProcessing;
+ }
+ }
+
+
+ // Check for and handle a request to cancel this operation.
+ if (localOp.getCancelRequest() != null)
+ {
+ localOp.indicateCancelled(localOp.getCancelRequest());
+ localOp.setProcessingStopTime();
+ return;
+ }
+
+
+ // Actually perform the add operation. This should also include taking
+ // care of any synchronization that might be needed.
+ Backend backend = DirectoryServer.getBackend(entryDN);
+ if (backend == null)
+ {
+ localOp.setResultCode(ResultCode.NO_SUCH_OBJECT);
+ localOp.appendErrorMessage("No backend for entry " +
+ entryDN.toString());
+ }
+ else
+ {
+ // 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:
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+ localOp.appendErrorMessage(getMessage(MSGID_ADD_SERVER_READONLY,
+ String.valueOf(entryDN)));
+ break addProcessing;
+
+ case INTERNAL_ONLY:
+ if (! (localOp.isInternalOperation() ||
+ localOp.isSynchronizationOperation()))
+ {
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+ localOp.appendErrorMessage(getMessage(
+ MSGID_ADD_SERVER_READONLY,
+ String.valueOf(entryDN)));
+ break addProcessing;
+ }
+ }
+
+ switch (backend.getWritabilityMode())
+ {
+ case DISABLED:
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+ localOp.appendErrorMessage(getMessage(
+ MSGID_ADD_BACKEND_READONLY,
+ String.valueOf(entryDN)));
+ break addProcessing;
+
+ case INTERNAL_ONLY:
+ if (! (localOp.isInternalOperation() ||
+ localOp.isSynchronizationOperation()))
+ {
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+ localOp.appendErrorMessage(getMessage(
+ MSGID_ADD_BACKEND_READONLY,
+ String.valueOf(entryDN)));
+ break addProcessing;
+ }
+ }
+ }
+
+
+ try
+ {
+ if (noOp)
+ {
+ localOp.appendErrorMessage(getMessage(MSGID_ADD_NOOP));
+
+ // FIXME -- We must set a result code other than SUCCESS.
+ }
+ else
+ {
+ for (SynchronizationProvider provider :
+ DirectoryServer.getSynchronizationProviders())
+ {
+ try
+ {
+ SynchronizationProviderResult result =
+ provider.doPreOperation(localOp);
+ if (! result.continueOperationProcessing())
+ {
+ break addProcessing;
+ }
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ logError(ErrorLogCategory.SYNCHRONIZATION,
+ ErrorLogSeverity.SEVERE_ERROR,
+ MSGID_ADD_SYNCH_PREOP_FAILED,
+ localOp.getConnectionID(),
+ localOp.getOperationID(),
+ getExceptionMessage(de));
+
+ localOp.setResponseData(de);
+ break addProcessing;
+ }
+ }
+
+ backend.addEntry(entry, localOp);
+ }
+
+ if (postReadRequest != null)
+ {
+ 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);
+
+ localOp.addResponseControl(responseControl);
+ }
+
+ localOp.setResultCode(ResultCode.SUCCESS);
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResultCode(de.getResultCode());
+ localOp.appendErrorMessage(de.getErrorMessage());
+ localOp.setMatchedDN(de.getMatchedDN());
+ localOp.setReferralURLs(de.getReferralURLs());
+
+ break addProcessing;
+ }
+ catch (CancelledOperationException coe)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, coe);
+ }
+
+ CancelResult cancelResult = coe.getCancelResult();
+
+ localOp.setCancelResult(cancelResult);
+ localOp.setResultCode(cancelResult.getResultCode());
+
+ String message = coe.getMessage();
+ if ((message != null) && (message.length() > 0))
+ {
+ localOp.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(localOp);
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ logError(ErrorLogCategory.SYNCHRONIZATION,
+ ErrorLogSeverity.SEVERE_ERROR,
+ MSGID_ADD_SYNCH_POSTOP_FAILED,
+ localOp.getConnectionID(),
+ localOp.getOperationID(),
+ getExceptionMessage(de));
+
+ localOp.setResponseData(de);
+ break;
+ }
+ }
+ }
+ }
+
+
+ // Indicate that it is now too late to attempt to cancel the operation.
+ localOp.setCancelResult(CancelResult.TOO_LATE);
+
+
+ // Invoke the post-operation add plugins.
+ if (! skipPostOperation)
+ {
+ // FIXME -- Should this also be done while holding the locks?
+ PostOperationPluginResult postOpResult =
+ pluginConfigManager.invokePostOperationAddPlugins(localOp);
+ if (postOpResult.connectionTerminated())
+ {
+ // There's no point in continuing with anything. Log the result and
+ // return.
+ localOp.setResultCode(ResultCode.CANCELED);
+
+ int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
+ localOp.appendErrorMessage(getMessage(msgID));
+
+ localOp.setProcessingStopTime();
+ return;
+ }
+ }
+
+
+ // Notify any change notification listeners that might be registered with
+ // the server.
+ if ((localOp.getResultCode() == ResultCode.SUCCESS) &&
+ (localOp.getEntryToAdd() != null))
+ {
+ for (ChangeNotificationListener changeListener :
+ DirectoryServer.getChangeNotificationListeners())
+ {
+ try
+ {
+ changeListener.handleAddOperation(localOp, localOp.getEntryToAdd());
+ }
+ catch (Exception e)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+
+ int msgID = MSGID_ADD_ERROR_NOTIFYING_CHANGE_LISTENER;
+ String message = getMessage(msgID, getExceptionMessage(e));
+ logError(ErrorLogCategory.CORE_SERVER, ErrorLogSeverity.SEVERE_ERROR,
+ message, msgID);
+ }
+ }
+ }
+
+
+ // Stop the processing timer.
+ localOp.setProcessingStopTime();
+
+ }
+
+
+ /**
+ * Perform a delete operation against a local backend.
+ *
+ * @param operation - The operation to perform
+ */
+ public void processDelete(DeleteOperation operation){
+ LocalBackendDeleteOperation localOperation =
+ new LocalBackendDeleteOperation(operation);
+ processLocalDelete(localOperation);
+ }
+
+ /**
+ * Perform a local delete operation against a local backend.
+ *
+ * @param operation - The operation to perform
+ */
+ private void processLocalDelete(LocalBackendDeleteOperation localOp)
+ {
+ ClientConnection clientConnection = localOp.getClientConnection();
+
+ // Get the plugin config manager that will be used for invoking plugins.
+ PluginConfigManager pluginConfigManager =
+ DirectoryServer.getPluginConfigManager();
+ boolean skipPostOperation = false;
+
+ // Check for and handle a request to cancel this operation.
+ if (localOp.getCancelRequest() != null)
+ {
+ localOp.indicateCancelled(localOp.getCancelRequest());
+ localOp.setProcessingStopTime();
+ return;
+ }
+
+ // Create a labeled block of code that we can break out of if a problem is
+ // detected.
+deleteProcessing:
+ {
+ // Process the entry DN to convert it from its raw form as provided by the
+ // client to the form required for the rest of the delete processing.
+ DN entryDN = localOp.getEntryDN();
+ if (entryDN == null){
+ break deleteProcessing;
+ }
+
+ // Grab a write lock on the entry.
+ Lock entryLock = null;
+ for (int i=0; i < 3; i++)
+ {
+ entryLock = LockManager.lockWrite(entryDN);
+ if (entryLock != null)
+ {
+ break;
+ }
+ }
+
+ if (entryLock == null)
+ {
+ localOp.setResultCode(DirectoryServer.getServerErrorResultCode());
+ localOp.appendErrorMessage(getMessage(MSGID_DELETE_CANNOT_LOCK_ENTRY,
+ String.valueOf(entryDN)));
+ break deleteProcessing;
+ }
+
+ Entry entry = null;
+ try
+ {
+ // Get the entry to delete. If it doesn't exist, then fail.
+ try
+ {
+ entry = backend.getEntry(entryDN);
+ localOp.setEntryToDelete(entry);
+ if (entry == null)
+ {
+ localOp.setResultCode(ResultCode.NO_SUCH_OBJECT);
+ localOp.appendErrorMessage(getMessage(MSGID_DELETE_NO_SUCH_ENTRY,
+ String.valueOf(entryDN)));
+
+ try
+ {
+ DN parentDN = entryDN.getParentDNInSuffix();
+ while (parentDN != null)
+ {
+ if (DirectoryServer.entryExists(parentDN))
+ {
+ localOp.setMatchedDN(parentDN);
+ break;
+ }
+
+ parentDN = parentDN.getParentDNInSuffix();
+ }
+ }
+ catch (Exception e)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+ }
+
+ break deleteProcessing;
+ }
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResultCode(de.getResultCode());
+ localOp.appendErrorMessage(de.getErrorMessage());
+ localOp.setMatchedDN(de.getMatchedDN());
+ localOp.setReferralURLs(de.getReferralURLs());
+ break deleteProcessing;
+ }
+
+
+ // Invoke any conflict resolution processing that might be needed by the
+ // synchronization provider.
+ for (SynchronizationProvider provider :
+ DirectoryServer.getSynchronizationProviders())
+ {
+ try
+ {
+ SynchronizationProviderResult result =
+ provider.handleConflictResolution(localOp);
+ if (! result.continueOperationProcessing())
+ {
+ break deleteProcessing;
+ }
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ logError(ErrorLogCategory.SYNCHRONIZATION,
+ ErrorLogSeverity.SEVERE_ERROR,
+ MSGID_DELETE_SYNCH_CONFLICT_RESOLUTION_FAILED,
+ localOp.getConnectionID(),
+ localOp.getOperationID(),
+ getExceptionMessage(de));
+
+ localOp.setResponseData(de);
+ break deleteProcessing;
+ }
+ }
+
+ // Check to see if the client has permission to perform the
+ // delete.
+
+ // Check to see if there are any controls in the request. If so, then
+ // see if there is any special processing required.
+ boolean noOp = false;
+ LDAPPreReadRequestControl preReadRequest = null;
+ List<Control> requestControls =
+ localOp.getRequestControls();
+ if ((requestControls != null) && (! requestControls.isEmpty()))
+ {
+ for (int i=0; i < requestControls.size(); i++)
+ {
+ Control c = requestControls.get(i);
+ String oid = c.getOID();
+
+ 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);
+ }
+
+ localOp.setResultCode(ResultCode.valueOf(le.getResultCode()));
+ localOp.appendErrorMessage(le.getMessage());
+
+ break deleteProcessing;
+ }
+ }
+
+ try
+ {
+ // FIXME -- We need to determine whether the current user has
+ // permission to make this determination.
+ SearchFilter filter = assertControl.getSearchFilter();
+ if (! filter.matchesEntry(entry))
+ {
+ localOp.setResultCode(ResultCode.ASSERTION_FAILED);
+
+ localOp.appendErrorMessage(getMessage(
+ MSGID_DELETE_ASSERTION_FAILED,
+ String.valueOf(entryDN)));
+
+ break deleteProcessing;
+ }
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResultCode(ResultCode.PROTOCOL_ERROR);
+
+ int msgID = MSGID_DELETE_CANNOT_PROCESS_ASSERTION_FILTER;
+ localOp.appendErrorMessage(getMessage(msgID,
+ String.valueOf(entryDN),
+ de.getErrorMessage()));
+
+ break deleteProcessing;
+ }
+ }
+ else if (oid.equals(OID_LDAP_NOOP_OPENLDAP_ASSIGNED))
+ {
+ noOp = true;
+ }
+ else if (oid.equals(OID_LDAP_READENTRY_PREREAD))
+ {
+ if (c instanceof LDAPAssertionRequestControl)
+ {
+ preReadRequest = (LDAPPreReadRequestControl) c;
+ }
+ else
+ {
+ try
+ {
+ preReadRequest = LDAPPreReadRequestControl.decodeControl(c);
+ requestControls.set(i, preReadRequest);
+ }
+ catch (LDAPException le)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, le);
+ }
+
+ localOp.setResultCode(ResultCode.valueOf(le.getResultCode()));
+ localOp.appendErrorMessage(le.getMessage());
+
+ break deleteProcessing;
+ }
+ }
+ }
+ 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,
+ localOp))
+ {
+ int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
+ localOp.appendErrorMessage(getMessage(msgID));
+ localOp.setResultCode(ResultCode.AUTHORIZATION_DENIED);
+ break deleteProcessing;
+ }
+
+
+ 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);
+ }
+
+ localOp.setResultCode(ResultCode.valueOf(le.getResultCode()));
+ localOp.appendErrorMessage(le.getMessage());
+
+ break deleteProcessing;
+ }
+ }
+
+
+ Entry authorizationEntry;
+ try
+ {
+ authorizationEntry = proxyControl.getAuthorizationEntry();
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResultCode(de.getResultCode());
+ localOp.appendErrorMessage(de.getErrorMessage());
+
+ break deleteProcessing;
+ }
+
+ if (AccessControlConfigManager.getInstance()
+ .getAccessControlHandler().isProxiedAuthAllowed(localOp,
+ authorizationEntry) == false) {
+ localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+
+ int msgID = MSGID_DELETE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
+ localOp.appendErrorMessage(
+ getMessage(msgID, String.valueOf(entryDN)));
+
+ skipPostOperation = true;
+ break deleteProcessing;
+ }
+ localOp.setAuthorizationEntry(authorizationEntry);
+ if (authorizationEntry == null)
+ {
+ localOp.setProxiedAuthorizationDN(DN.nullDN());
+ }
+ else
+ {
+ localOp.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,
+ localOp))
+ {
+ int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
+ localOp.appendErrorMessage(getMessage(msgID));
+ localOp.setResultCode(ResultCode.AUTHORIZATION_DENIED);
+ break deleteProcessing;
+ }
+
+
+ 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);
+ }
+
+ localOp.setResultCode(ResultCode.valueOf(le.getResultCode()));
+ localOp.appendErrorMessage(le.getMessage());
+
+ break deleteProcessing;
+ }
+ }
+
+
+ Entry authorizationEntry;
+ try
+ {
+ authorizationEntry = proxyControl.getAuthorizationEntry();
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResultCode(de.getResultCode());
+ localOp.appendErrorMessage(de.getErrorMessage());
+
+ break deleteProcessing;
+ }
+
+ if (AccessControlConfigManager.getInstance()
+ .getAccessControlHandler().isProxiedAuthAllowed(localOp,
+ authorizationEntry) == false) {
+ localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+
+ int msgID = MSGID_DELETE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
+ localOp.appendErrorMessage(
+ getMessage(msgID, String.valueOf(entryDN)));
+
+ skipPostOperation = true;
+ break deleteProcessing;
+ }
+ localOp.setAuthorizationEntry(authorizationEntry);
+ if (authorizationEntry == null)
+ {
+ localOp.setProxiedAuthorizationDN(DN.nullDN());
+ }
+ else
+ {
+ localOp.setProxiedAuthorizationDN(authorizationEntry.getDN());
+ }
+ }
+
+ // NYI -- Add support for additional controls.
+ else if (c.isCritical())
+ {
+ Backend backend = DirectoryServer.getBackend(entryDN);
+ if ((backend == null) || (! backend.supportsControl(oid)))
+ {
+ localOp.setResultCode(
+ ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
+
+ int msgID = MSGID_DELETE_UNSUPPORTED_CRITICAL_CONTROL;
+ localOp.appendErrorMessage(getMessage(msgID,
+ String.valueOf(entryDN),
+ oid));
+
+ break deleteProcessing;
+ }
+ }
+ }
+ }
+
+
+ // 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 may
+ // have already exposed sensitive information to the client.
+ if (AccessControlConfigManager.getInstance()
+ .getAccessControlHandler().isAllowed(localOp) == false) {
+ localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+
+ int msgID = MSGID_DELETE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
+ localOp.appendErrorMessage(getMessage(msgID,
+ String.valueOf(entryDN)));
+
+ skipPostOperation = true;
+ break deleteProcessing;
+ }
+
+ // Check for and handle a request to cancel this operation.
+ if (localOp.getCancelRequest() != null)
+ {
+ localOp.indicateCancelled(localOp.getCancelRequest());
+ localOp.setProcessingStopTime();
+ return;
+ }
+
+
+ // If the operation is not a synchronization operation,
+ // invoke the pre-delete plugins.
+ if (!localOp.isSynchronizationOperation())
+ {
+ PreOperationPluginResult preOpResult =
+ pluginConfigManager.invokePreOperationDeletePlugins(localOp);
+ if (preOpResult.connectionTerminated())
+ {
+ // There's no point in continuing with anything. Log the request
+ // and result and return.
+ localOp.setResultCode(ResultCode.CANCELED);
+
+ int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
+ localOp.appendErrorMessage(getMessage(msgID));
+
+ localOp.setProcessingStopTime();
+ return;
+ }
+ else if (preOpResult.sendResponseImmediately())
+ {
+ skipPostOperation = true;
+ break deleteProcessing;
+ }
+ else if (preOpResult.skipCoreProcessing())
+ {
+ skipPostOperation = false;
+ break deleteProcessing;
+ }
+ }
+
+
+ // Check for and handle a request to cancel this operation.
+ if (localOp.getCancelRequest() != null)
+ {
+ localOp.indicateCancelled(localOp.getCancelRequest());
+ localOp.setProcessingStopTime();
+ return;
+ }
+
+
+ // Get the backend to use for the delete. If there is none, then fail.
+ if (backend == null)
+ {
+ localOp.setResultCode(ResultCode.NO_SUCH_OBJECT);
+ localOp.appendErrorMessage(getMessage(MSGID_DELETE_NO_SUCH_ENTRY,
+ String.valueOf(entryDN)));
+ break deleteProcessing;
+ }
+
+
+ // 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:
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+ localOp.appendErrorMessage(getMessage(
+ MSGID_DELETE_SERVER_READONLY,
+ String.valueOf(entryDN)));
+ break deleteProcessing;
+
+ case INTERNAL_ONLY:
+ if (! (localOp.isInternalOperation() ||
+ localOp.isSynchronizationOperation()))
+ {
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+ localOp.appendErrorMessage(getMessage(
+ MSGID_DELETE_SERVER_READONLY,
+ String.valueOf(entryDN)));
+ break deleteProcessing;
+ }
+ }
+
+ switch (backend.getWritabilityMode())
+ {
+ case DISABLED:
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+ localOp.appendErrorMessage(getMessage(
+ MSGID_DELETE_BACKEND_READONLY,
+ String.valueOf(entryDN)));
+ break deleteProcessing;
+
+ case INTERNAL_ONLY:
+ if (! (localOp.isInternalOperation() ||
+ localOp.isSynchronizationOperation()))
+ {
+ localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+ localOp.appendErrorMessage(getMessage(
+ MSGID_DELETE_BACKEND_READONLY,
+ String.valueOf(entryDN)));
+ break deleteProcessing;
+ }
+ }
+ }
+
+
+ // The selected backend will have the responsibility of making sure that
+ // the entry actually exists and does not have any children (or possibly
+ // handling a subtree delete). But we will need to check if there are
+ // any subordinate backends that should stop us from attempting the
+ // delete.
+ Backend[] subBackends = backend.getSubordinateBackends();
+ for (Backend b : subBackends)
+ {
+ DN[] baseDNs = b.getBaseDNs();
+ for (DN dn : baseDNs)
+ {
+ if (dn.isDescendantOf(entryDN))
+ {
+ localOp.setResultCode(ResultCode.NOT_ALLOWED_ON_NONLEAF);
+ localOp.appendErrorMessage(getMessage(
+ MSGID_DELETE_HAS_SUB_BACKEND,
+ String.valueOf(entryDN),
+ String.valueOf(dn)));
+ break deleteProcessing;
+ }
+ }
+ }
+
+
+ // Actually perform the delete.
+ try
+ {
+ if (noOp)
+ {
+ localOp.appendErrorMessage(getMessage(MSGID_DELETE_NOOP));
+
+ // FIXME -- We must set a result code other than SUCCESS.
+ }
+ else
+ {
+ for (SynchronizationProvider provider :
+ DirectoryServer.getSynchronizationProviders())
+ {
+ try
+ {
+ SynchronizationProviderResult result =
+ provider.doPreOperation(localOp);
+ if (! result.continueOperationProcessing())
+ {
+ break deleteProcessing;
+ }
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ logError(ErrorLogCategory.SYNCHRONIZATION,
+ ErrorLogSeverity.SEVERE_ERROR,
+ MSGID_DELETE_SYNCH_PREOP_FAILED,
+ localOp.getConnectionID(),
+ localOp.getOperationID(),
+ getExceptionMessage(de));
+
+ localOp.setResponseData(de);
+ break deleteProcessing;
+ }
+ }
+
+ backend.deleteEntry(entryDN, localOp);
+ }
+
+ if (preReadRequest != null)
+ {
+ Entry entryCopy = entry.duplicate(true);
+
+ if (! preReadRequest.allowsAttribute(
+ DirectoryServer.getObjectClassAttributeType()))
+ {
+ entryCopy.removeAttribute(
+ DirectoryServer.getObjectClassAttributeType());
+ }
+
+ if (! preReadRequest.returnAllUserAttributes())
+ {
+ Iterator<AttributeType> iterator =
+ entryCopy.getUserAttributes().keySet().iterator();
+ while (iterator.hasNext())
+ {
+ AttributeType attrType = iterator.next();
+ if (! preReadRequest.allowsAttribute(attrType))
+ {
+ iterator.remove();
+ }
+ }
+ }
+
+ if (! preReadRequest.returnAllOperationalAttributes())
+ {
+ Iterator<AttributeType> iterator =
+ entryCopy.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(entryCopy);
+ LDAPPreReadResponseControl responseControl =
+ new LDAPPreReadResponseControl(preReadRequest.getOID(),
+ preReadRequest.isCritical(),
+ searchEntry);
+
+ localOp.addResponseControl(responseControl);
+ }
+
+ localOp.setResultCode(ResultCode.SUCCESS);
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ localOp.setResultCode(de.getResultCode());
+ localOp.appendErrorMessage(de.getErrorMessage());
+ localOp.setMatchedDN(de.getMatchedDN());
+ localOp.setReferralURLs(de.getReferralURLs());
+
+ break deleteProcessing;
+ }
+ catch (CancelledOperationException coe)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, coe);
+ }
+
+ CancelResult cancelResult = coe.getCancelResult();
+
+ localOp.setCancelResult(cancelResult);
+ localOp.setResultCode(cancelResult.getResultCode());
+
+ String message = coe.getMessage();
+ if ((message != null) && (message.length() > 0))
+ {
+ localOp.appendErrorMessage(message);
+ }
+
+ break deleteProcessing;
+ }
+ }
+ finally
+ {
+ LockManager.unlock(entryDN, entryLock);
+
+ for (SynchronizationProvider provider :
+ DirectoryServer.getSynchronizationProviders())
+ {
+ try
+ {
+ provider.doPostOperation(localOp);
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ logError(ErrorLogCategory.SYNCHRONIZATION,
+ ErrorLogSeverity.SEVERE_ERROR,
+ MSGID_DELETE_SYNCH_POSTOP_FAILED,
+ localOp.getConnectionID(),
+ localOp.getOperationID(),
+ getExceptionMessage(de));
+
+ localOp.setResponseData(de);
+ break;
+ }
+ }
+ }
+ }
+
+
+ // Indicate that it is now too late to attempt to cancel the operation.
+ localOp.setCancelResult(CancelResult.TOO_LATE);
+
+
+ // Invoke the post-operation delete plugins.
+ if (! skipPostOperation)
+ {
+ PostOperationPluginResult postOperationResult =
+ pluginConfigManager.invokePostOperationDeletePlugins(localOp);
+ if (postOperationResult.connectionTerminated())
+ {
+ localOp.setResultCode(ResultCode.CANCELED);
+
+ int msgID = MSGID_CANCELED_BY_POSTOP_DISCONNECT;
+ localOp.appendErrorMessage(getMessage(msgID));
+
+ localOp.setProcessingStopTime();
+ return;
+ }
+ }
+
+
+ // Notify any change notification listeners that might be registered with
+ // the server.
+ if (localOp.getResultCode() == ResultCode.SUCCESS)
+ {
+ for (ChangeNotificationListener changeListener :
+ DirectoryServer.getChangeNotificationListeners())
+ {
+ try
+ {
+ changeListener.handleDeleteOperation(localOp,
+ localOp.getEntryToDelete());
+ }
+ catch (Exception e)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+
+ int msgID = MSGID_DELETE_ERROR_NOTIFYING_CHANGE_LISTENER;
+ String message = getMessage(msgID, getExceptionMessage(e));
+ logError(ErrorLogCategory.CORE_SERVER, ErrorLogSeverity.SEVERE_ERROR,
+ message, msgID);
+ }
+ }
+ }
+
+ // Stop the processing timer.
+ localOp.setProcessingStopTime();
+ }
+
+
+ /**
+ * Attaches the current local operation to the global operation so that
+ * operation runner can execute local operation post response later on.
+ *
+ * @param <O> subtype of Operation
+ * @param <L> subtype of LocalBackendOperation
+ * @param globalOperation the global operation to which local operation
+ * should be attached to
+ * @param currentLocalOperation the local operation to attach to the global
+ * operation
+ */
+ @SuppressWarnings("unchecked")
+ public static final <O extends Operation, L>
+ void attachLocalOperation (
+ O globalOperation,
+ L currentLocalOperation
+ )
+ {
+ List<?> existingAttachment =
+ (List<?>) globalOperation.getAttachment(Operation.LOCALBACKENDOPERATIONS);
+
+ List<L> newAttachment = new ArrayList<L>();
+
+ if (existingAttachment != null)
+ {
+ // This line raises an unchecked conversion warning.
+ // There is nothing we can do to prevent this warning
+ // so let's get rid of it since we know the cast is safe.
+ newAttachment.addAll ((List<L>) existingAttachment);
+ }
+ newAttachment.add (currentLocalOperation);
+ globalOperation.setAttachment(
+ Operation.LOCALBACKENDOPERATIONS, newAttachment);
+ }
+
+}
--
Gitblit v1.10.0