| | |
| | | */ |
| | | package org.opends.server.workflowelement.localbackend; |
| | | |
| | | import org.opends.messages.Message; |
| | | import org.opends.messages.MessageBuilder; |
| | | |
| | | import static org.opends.server.config.ConfigConstants.*; |
| | | import static org.opends.server.loggers.debug.DebugLogger.debugEnabled; |
| | | import static org.opends.messages.CoreMessages.*; |
| | | import static org.opends.messages.ToolMessages.*; |
| | | 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.TreeMap; |
| | | import java.util.concurrent.locks.Lock; |
| | | |
| | | import org.opends.server.admin.std.meta.PasswordPolicyCfgDefn; |
| | | 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.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.CompareOperation; |
| | | import org.opends.server.core.DeleteOperation; |
| | | import org.opends.server.core.DirectoryServer; |
| | | import org.opends.server.core.ModifyDNOperation; |
| | | 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.AccountStatusNotification; |
| | | 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.LockManager; |
| | | import org.opends.server.types.Modification; |
| | | import org.opends.server.types.ModificationType; |
| | | import org.opends.server.types.ObjectClass; |
| | | import org.opends.server.types.Operation; |
| | | 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.WritabilityMode; |
| | | 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 |
| | |
| | | */ |
| | | public class LocalBackendWorkflowElement extends LeafWorkflowElement |
| | | { |
| | | /** |
| | | * The tracer object for the debug logger. |
| | | */ |
| | | private static final DebugTracer TRACER = DebugLogger.getTracer(); |
| | | |
| | | // the backend associated with the local workflow element |
| | | private Backend backend; |
| | | |
| | | // the set of local backend workflow elements registered with the server |
| | | private static |
| | | TreeMap<String, LocalBackendWorkflowElement> registeredLocalBackends = |
| | | new TreeMap<String, LocalBackendWorkflowElement>(); |
| | | private static TreeMap<String, LocalBackendWorkflowElement> |
| | | registeredLocalBackends = |
| | | new TreeMap<String, LocalBackendWorkflowElement>(); |
| | | |
| | | // a lock to guarantee safe concurrent access to the registeredLocalBackends |
| | | // variable |
| | | private static Object registeredLocalBackendsLock = new Object(); |
| | | |
| | | |
| | | |
| | | /** |
| | | * Creates a new instance of the local backend workflow element. |
| | | * |
| | | * @param workflowElementID the workflow element identifier |
| | | * @param backend the backend associated to that workflow element |
| | | */ |
| | | private LocalBackendWorkflowElement( |
| | | String workflowElementID, |
| | | Backend backend |
| | | ) |
| | | private LocalBackendWorkflowElement(String workflowElementID, Backend backend) |
| | | { |
| | | super(workflowElementID); |
| | | |
| | | this.backend = backend; |
| | | |
| | | if (this.backend != null) |
| | | { |
| | | isPrivate = this.backend.isPrivateBackend(); |
| | | } |
| | | setPrivate(backend.isPrivateBackend()); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Creates and registers a local backend with the server. |
| | | * |
| | |
| | | * already created or a newly created local backend workflow |
| | | * element. |
| | | */ |
| | | public static LocalBackendWorkflowElement create( |
| | | String workflowElementID, |
| | | Backend backend |
| | | ) |
| | | public static LocalBackendWorkflowElement create(String workflowElementID, |
| | | Backend backend) |
| | | { |
| | | LocalBackendWorkflowElement localBackend = null; |
| | | |
| | |
| | | localBackend = registeredLocalBackends.get(workflowElementID); |
| | | if (localBackend == null) |
| | | { |
| | | localBackend = new LocalBackendWorkflowElement( |
| | | workflowElementID, backend); |
| | | localBackend = new LocalBackendWorkflowElement(workflowElementID, |
| | | backend); |
| | | |
| | | // store the new local backend in the list of registered backends |
| | | registerLocalBackend(localBackend); |
| | |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Removes a local backend that was registered with the server. |
| | | * |
| | | * @param workflowElementID the identifier of the workflow element to remove |
| | | */ |
| | | public static void remove( |
| | | String workflowElementID |
| | | ) |
| | | public static void remove(String workflowElementID) |
| | | { |
| | | deregisterLocalBackend(workflowElementID); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Removes all the local backends that were registered with the server. |
| | | * This function is intended to be called when the server is shutting down. |
| | |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Registers a local backend with the server. |
| | | * |
| | | * @param localBackend the local backend to register with the server |
| | | */ |
| | | private static void registerLocalBackend( |
| | | LocalBackendWorkflowElement localBackend |
| | | ) |
| | | LocalBackendWorkflowElement localBackend) |
| | | { |
| | | synchronized (registeredLocalBackendsLock) |
| | | { |
| | |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Deregisters a local backend with the server. |
| | | * |
| | | * @param workflowElementID the identifier of the workflow element to remove |
| | | */ |
| | | private static void deregisterLocalBackend( |
| | | String workflowElementID |
| | | ) |
| | | private static void deregisterLocalBackend(String workflowElementID) |
| | | { |
| | | synchronized (registeredLocalBackendsLock) |
| | | { |
| | |
| | | if (existingLocalBackend != null) |
| | | { |
| | | TreeMap<String, LocalBackendWorkflowElement> newLocalBackends = |
| | | new TreeMap |
| | | <String, LocalBackendWorkflowElement>(registeredLocalBackends); |
| | | new TreeMap<String, LocalBackendWorkflowElement>( |
| | | registeredLocalBackends); |
| | | newLocalBackends.remove(workflowElementID); |
| | | registeredLocalBackends = newLocalBackends; |
| | | } |
| | |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | |
| | | { |
| | | 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; |
| | | case MODIFY_DN: |
| | | processModifyDN((ModifyDNOperation) operation); |
| | | break; |
| | | case COMPARE: |
| | | processCompare((CompareOperation) operation); |
| | | break; |
| | | case ABANDON: |
| | | // There is no processing for an abandon operation. |
| | | break; |
| | | default: |
| | | // jdemendi - temporary code, just make sure that we are not falling |
| | | // into that incomplete code... |
| | | Validator.ensureTrue(false); |
| | | break; |
| | | case BIND: |
| | | LocalBackendBindOperation bindOperation = |
| | | new LocalBackendBindOperation((BindOperation) operation); |
| | | bindOperation.processLocalBind(backend); |
| | | break; |
| | | |
| | | case SEARCH: |
| | | LocalBackendSearchOperation searchOperation = |
| | | new LocalBackendSearchOperation((SearchOperation) operation); |
| | | searchOperation.processLocalSearch(backend); |
| | | break; |
| | | |
| | | case ADD: |
| | | LocalBackendAddOperation addOperation = |
| | | new LocalBackendAddOperation((AddOperation) operation); |
| | | addOperation.processLocalAdd(backend); |
| | | break; |
| | | |
| | | case DELETE: |
| | | LocalBackendDeleteOperation deleteOperation = |
| | | new LocalBackendDeleteOperation((DeleteOperation) operation); |
| | | deleteOperation.processLocalDelete(backend); |
| | | break; |
| | | |
| | | case MODIFY: |
| | | LocalBackendModifyOperation modifyOperation = |
| | | new LocalBackendModifyOperation((ModifyOperation) operation); |
| | | modifyOperation.processLocalModify(backend); |
| | | break; |
| | | |
| | | case MODIFY_DN: |
| | | LocalBackendModifyDNOperation modifyDNOperation = |
| | | new LocalBackendModifyDNOperation((ModifyDNOperation) operation); |
| | | modifyDNOperation.processLocalModifyDN(backend); |
| | | break; |
| | | |
| | | case COMPARE: |
| | | LocalBackendCompareOperation compareOperation = |
| | | new LocalBackendCompareOperation((CompareOperation) operation); |
| | | compareOperation.processLocalCompare(backend); |
| | | break; |
| | | |
| | | case ABANDON: |
| | | // There is no processing for an abandon operation. |
| | | break; |
| | | |
| | | default: |
| | | throw new AssertionError("Attempted to execute an invalid operation " + |
| | | "type: " + operation.getOperationType() + |
| | | " (" + operation + ")"); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 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 localOp - 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. |
| | | boolean pwPolicyControlRequested = false; |
| | | PasswordPolicyErrorType pwpErrorType = null; |
| | | 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( |
| | | ERR_MODIFY_NO_MODIFICATIONS.get(String.valueOf(entryDN))); |
| | | break modifyProcessing; |
| | | } |
| | | |
| | | |
| | | // If the user must change their password before doing anything else, and |
| | | // if the target of the modify operation isn't the user's own entry, then |
| | | // reject the request. |
| | | if ((! 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. Also note that we haven't yet checked the |
| | | // request controls so we need to do that now to see if the password |
| | | // policy request control was provided. |
| | | for (Control c : localOp.getRequestControls()) |
| | | { |
| | | if (c.getOID().equals(OID_PASSWORD_POLICY_CONTROL)) |
| | | { |
| | | pwPolicyControlRequested = true; |
| | | pwpErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET; |
| | | break; |
| | | } |
| | | } |
| | | |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | |
| | | |
| | | localOp.appendErrorMessage(ERR_MODIFY_MUST_CHANGE_PASSWORD.get()); |
| | | break modifyProcessing; |
| | | } |
| | | } |
| | | |
| | | |
| | | // Check for a request to cancel this operation. |
| | | if (localOp.getCancelRequest() != null) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | // Acquire a write lock on the target entry. |
| | | Lock entryLock = null; |
| | | for (int i=0; i < 3; i++) |
| | | { |
| | | entryLock = LockManager.lockWrite(entryDN); |
| | | if (entryLock != null) |
| | | { |
| | | break; |
| | | } |
| | | } |
| | | |
| | | if (entryLock == null) |
| | | { |
| | | localOp.setResultCode(DirectoryServer.getServerErrorResultCode()); |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_CANNOT_LOCK_ENTRY.get(String.valueOf(entryDN))); |
| | | |
| | | skipPostOperation = true; |
| | | break modifyProcessing; |
| | | } |
| | | |
| | | |
| | | try |
| | | { |
| | | // Check for a request to cancel this operation. |
| | | if (localOp.getCancelRequest() != null) |
| | | { |
| | | 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.setResponseData(de); |
| | | break modifyProcessing; |
| | | } |
| | | |
| | | if (currentEntry == null) |
| | | { |
| | | localOp.setResultCode(ResultCode.NO_SUCH_OBJECT); |
| | | localOp.appendErrorMessage(ERR_MODIFY_NO_SUCH_ENTRY.get( |
| | | String.valueOf(entryDN))); |
| | | |
| | | // See if one of the entry's ancestors exists. |
| | | DN parentDN = entryDN.getParentDNInSuffix(); |
| | | while (parentDN != null) |
| | | { |
| | | try |
| | | { |
| | | if (DirectoryServer.entryExists(parentDN)) |
| | | { |
| | | 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 (!AccessControlConfigManager.getInstance(). |
| | | getAccessControlHandler(). |
| | | isAllowed(entryDN, localOp, c)) |
| | | { |
| | | localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid)); |
| | | skipPostOperation = true; |
| | | break modifyProcessing; |
| | | } |
| | | |
| | | 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.getMessageObject()); |
| | | |
| | | 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( |
| | | ERR_MODIFY_ASSERTION_FAILED.get(String.valueOf(entryDN))); |
| | | |
| | | break modifyProcessing; |
| | | } |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | localOp.setResultCode(ResultCode.PROTOCOL_ERROR); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_CANNOT_PROCESS_ASSERTION_FILTER.get( |
| | | String.valueOf(entryDN), |
| | | de.getMessageObject())); |
| | | |
| | | break modifyProcessing; |
| | | } |
| | | } |
| | | else if (oid.equals(OID_LDAP_NOOP_OPENLDAP_ASSIGNED)) |
| | | { |
| | | noOp = true; |
| | | } |
| | | else if (oid.equals(OID_LDAP_READENTRY_PREREAD)) |
| | | { |
| | | if (c instanceof LDAPPreReadRequestControl) |
| | | { |
| | | preReadRequest = (LDAPPreReadRequestControl) c; |
| | | } |
| | | else |
| | | { |
| | | try |
| | | { |
| | | preReadRequest = LDAPPreReadRequestControl.decodeControl(c); |
| | | requestControls.set(i, preReadRequest); |
| | | } |
| | | catch (LDAPException le) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, le); |
| | | } |
| | | |
| | | localOp.setResultCode(ResultCode.valueOf(le.getResultCode())); |
| | | localOp.appendErrorMessage(le.getMessageObject()); |
| | | |
| | | break modifyProcessing; |
| | | } |
| | | } |
| | | } |
| | | else if (oid.equals(OID_LDAP_READENTRY_POSTREAD)) |
| | | { |
| | | if (c instanceof LDAPPostReadRequestControl) |
| | | { |
| | | postReadRequest = (LDAPPostReadRequestControl) c; |
| | | } |
| | | else |
| | | { |
| | | try |
| | | { |
| | | postReadRequest = LDAPPostReadRequestControl.decodeControl(c); |
| | | requestControls.set(i, postReadRequest); |
| | | } |
| | | catch (LDAPException le) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, le); |
| | | } |
| | | |
| | | localOp.setResultCode(ResultCode.valueOf(le.getResultCode())); |
| | | localOp.appendErrorMessage(le.getMessageObject()); |
| | | |
| | | 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)) |
| | | { |
| | | localOp.appendErrorMessage( |
| | | ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get()); |
| | | 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.getMessageObject()); |
| | | |
| | | 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.getMessageObject()); |
| | | |
| | | 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)) |
| | | { |
| | | localOp.appendErrorMessage( |
| | | ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get()); |
| | | 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.getMessageObject()); |
| | | |
| | | 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.getMessageObject()); |
| | | |
| | | break modifyProcessing; |
| | | } |
| | | |
| | | localOp.setAuthorizationEntry(authorizationEntry); |
| | | if (authorizationEntry == null) |
| | | { |
| | | localOp.setProxiedAuthorizationDN(DN.nullDN()); |
| | | } |
| | | else |
| | | { |
| | | localOp.setProxiedAuthorizationDN(authorizationEntry.getDN()); |
| | | } |
| | | } |
| | | else if (oid.equals(OID_PASSWORD_POLICY_CONTROL)) |
| | | { |
| | | pwPolicyControlRequested = true; |
| | | } |
| | | |
| | | // NYI -- Add support for additional controls. |
| | | else if (c.isCritical()) |
| | | { |
| | | if ((backend == null) || (! backend.supportsControl(oid))) |
| | | { |
| | | localOp.setResultCode( |
| | | ResultCode.UNAVAILABLE_CRITICAL_EXTENSION); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_UNSUPPORTED_CRITICAL_CONTROL.get( |
| | | 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.getMessageObject()); |
| | | |
| | | 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(ERR_MODIFY_SYNCH_CONFLICT_RESOLUTION_FAILED. |
| | | get(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)) |
| | | { |
| | | pwpErrorType = |
| | | PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED; |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_PWRESET_INSUFFICIENT_PRIVILEGES.get()); |
| | | 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( |
| | | ERR_MODIFY_ATTR_IS_NO_USER_MOD.get(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.UNWILLING_TO_PERFORM); |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_ATTR_IS_OBSOLETE.get(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)) |
| | | { |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_CHANGE_PRIVILEGE_INSUFFICIENT_PRIVILEGES |
| | | .get()); |
| | | 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); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_PASSWORDS_CANNOT_HAVE_OPTIONS.get()); |
| | | break modifyProcessing; |
| | | } |
| | | |
| | | |
| | | // If it's a self change, then see if that's allowed. |
| | | if (selfChange && |
| | | (! pwPolicyState.getPolicy().allowUserPasswordChanges())) |
| | | { |
| | | pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED; |
| | | |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_NO_USER_PW_CHANGES.get()); |
| | | break modifyProcessing; |
| | | } |
| | | |
| | | |
| | | // If we require secure password changes, then makes sure it's a |
| | | // secure communication channel. |
| | | if (pwPolicyState.getPolicy().requireSecurePasswordChanges() && |
| | | (! clientConnection.isSecure())) |
| | | { |
| | | pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED; |
| | | |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_REQUIRE_SECURE_CHANGES.get()); |
| | | 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()) |
| | | { |
| | | pwpErrorType = PasswordPolicyErrorType.PASSWORD_TOO_YOUNG; |
| | | |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | |
| | | localOp.appendErrorMessage(ERR_MODIFY_WITHIN_MINIMUM_AGE.get()); |
| | | 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().allowMultiplePasswordValues()) && |
| | | (passwordsToAdd > 1)) |
| | | { |
| | | pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED; |
| | | |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_MULTIPLE_VALUES_NOT_ALLOWED.get()); |
| | | 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()) |
| | | { |
| | | pwpErrorType = |
| | | PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY; |
| | | |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_NO_PREENCODED_PASSWORDS.get()); |
| | | break modifyProcessing; |
| | | } |
| | | else |
| | | { |
| | | encodedValues.add(v); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | if (isAdd) |
| | | { |
| | | // Make sure that the password value doesn't already |
| | | // exist. |
| | | if (pwPolicyState.passwordMatches(v.getValue())) |
| | | { |
| | | pwpErrorType = |
| | | PasswordPolicyErrorType.PASSWORD_IN_HISTORY; |
| | | |
| | | localOp.setResultCode( |
| | | ResultCode.ATTRIBUTE_OR_VALUE_EXISTS); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_PASSWORD_EXISTS.get()); |
| | | 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.getMessageObject()); |
| | | 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) |
| | | { |
| | | pwpErrorType = |
| | | PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY; |
| | | |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_NO_PREENCODED_PASSWORDS.get()); |
| | | break modifyProcessing; |
| | | } |
| | | else |
| | | { |
| | | encodedValues.add(v); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | List<Attribute> attrList = currentEntry.getAttribute(t); |
| | | if ((attrList == null) || (attrList.isEmpty())) |
| | | { |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_NO_EXISTING_VALUES.get()); |
| | | 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()); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_CANNOT_DECODE_PW.get( |
| | | de.getMessageObject())); |
| | | 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()); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_CANNOT_DECODE_PW.get( |
| | | de.getMessageObject())); |
| | | 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); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_INVALID_PASSWORD.get()); |
| | | break modifyProcessing; |
| | | } |
| | | |
| | | currentPasswordProvided = true; |
| | | } |
| | | } |
| | | |
| | | a.setValues(encodedValues); |
| | | |
| | | break; |
| | | |
| | | default: |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_INVALID_MOD_TYPE_FOR_PASSWORD.get( |
| | | 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); |
| | | |
| | | Message message = |
| | | ERR_MODIFY_INVALID_DISABLED_VALUE.get( |
| | | OP_ATTR_ACCOUNT_DISABLED, |
| | | String.valueOf(de.getMessageObject())); |
| | | 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(ERR_MODIFY_ADD_NO_VALUES.get( |
| | | String.valueOf(entryDN), |
| | | a.getName())); |
| | | break modifyProcessing; |
| | | } |
| | | |
| | | // If the server is configured to check schema and the |
| | | // operation is not a synchronization operation, |
| | | // make sure that all the new values are valid according to the |
| | | // associated syntax. |
| | | if ((DirectoryServer.checkSchema()) && |
| | | (!localOp.isSynchronizationOperation()) ) |
| | | { |
| | | AcceptRejectWarn syntaxPolicy = |
| | | DirectoryServer.getSyntaxEnforcementPolicy(); |
| | | AttributeSyntax syntax = t.getSyntax(); |
| | | |
| | | if (syntaxPolicy == AcceptRejectWarn.REJECT) |
| | | { |
| | | MessageBuilder invalidReason = |
| | | new MessageBuilder(); |
| | | |
| | | for (AttributeValue v : newValues) |
| | | { |
| | | if (! syntax.valueIsAcceptable(v.getValue(), invalidReason)) |
| | | { |
| | | localOp.setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX); |
| | | |
| | | |
| | | localOp.appendErrorMessage(ERR_MODIFY_ADD_INVALID_SYNTAX |
| | | .get(String.valueOf(entryDN), |
| | | a.getName(), |
| | | v.getStringValue(), |
| | | invalidReason.toString())); |
| | | |
| | | break modifyProcessing; |
| | | } |
| | | } |
| | | } |
| | | else if (syntaxPolicy == AcceptRejectWarn.WARN) |
| | | { |
| | | MessageBuilder invalidReason = new MessageBuilder(); |
| | | |
| | | for (AttributeValue v : newValues) |
| | | { |
| | | if (! syntax.valueIsAcceptable(v.getValue(), invalidReason)) |
| | | { |
| | | localOp.setResultCode( |
| | | ResultCode.INVALID_ATTRIBUTE_SYNTAX); |
| | | |
| | | logError( |
| | | ERR_MODIFY_ADD_INVALID_SYNTAX. |
| | | get(String.valueOf(entryDN), a.getName(), |
| | | v.getStringValue(), invalidReason.toString())); |
| | | |
| | | invalidReason = new MessageBuilder(); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // Add the provided attribute or merge an existing attribute with |
| | | // the values of the new attribute. If there are any duplicates, |
| | | // then fail. |
| | | 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); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_ADD_DUPLICATE_VALUE.get(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); |
| | | |
| | | localOp.appendErrorMessage(ERR_MODIFY_DELETE_RDN_ATTR.get( |
| | | 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); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_DELETE_MISSING_VALUES.get( |
| | | String.valueOf(entryDN), |
| | | a.getName(), |
| | | buffer.toString())); |
| | | |
| | | break modifyProcessing; |
| | | } |
| | | } |
| | | else |
| | | { |
| | | localOp.setResultCode(ResultCode.NO_SUCH_ATTRIBUTE); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_DELETE_NO_SUCH_ATTR.get(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); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_DELETE_RDN_ATTR.get(String.valueOf(entryDN), |
| | | a.getName())); |
| | | break modifyProcessing; |
| | | } |
| | | break; |
| | | } |
| | | |
| | | // If the server is configured to check schema and the |
| | | // operation is not a synchronization operation, |
| | | // make sure that all the new values are valid according to the |
| | | // associated syntax. |
| | | newValues = a.getValues(); |
| | | if ((DirectoryServer.checkSchema()) && |
| | | (!localOp.isSynchronizationOperation()) ) |
| | | { |
| | | AcceptRejectWarn syntaxPolicy = |
| | | DirectoryServer.getSyntaxEnforcementPolicy(); |
| | | AttributeSyntax syntax = t.getSyntax(); |
| | | |
| | | if (syntaxPolicy == AcceptRejectWarn.REJECT) |
| | | { |
| | | MessageBuilder invalidReason = new MessageBuilder(); |
| | | |
| | | for (AttributeValue v : newValues) |
| | | { |
| | | if (! syntax.valueIsAcceptable(v.getValue(), invalidReason)) |
| | | { |
| | | localOp.setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_REPLACE_INVALID_SYNTAX.get( |
| | | String.valueOf(entryDN), |
| | | a.getName(), |
| | | v.getStringValue(), |
| | | invalidReason.toString())); |
| | | |
| | | break modifyProcessing; |
| | | } |
| | | } |
| | | } |
| | | else if (syntaxPolicy == AcceptRejectWarn.WARN) |
| | | { |
| | | MessageBuilder invalidReason = new MessageBuilder(); |
| | | for (AttributeValue v : newValues) |
| | | { |
| | | if (! syntax.valueIsAcceptable(v.getValue(), invalidReason)) |
| | | { |
| | | localOp.setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX); |
| | | |
| | | logError( |
| | | ERR_MODIFY_REPLACE_INVALID_SYNTAX. |
| | | get(String.valueOf(entryDN), a.getName(), |
| | | v.getStringValue(), invalidReason.toString())); |
| | | |
| | | invalidReason = new MessageBuilder(); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // If the provided attribute does not have any options, then we |
| | | // will simply use it in place of any existing attribute of the |
| | | // provided type (or add it if it doesn't exist). |
| | | if (! 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); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_DELETE_RDN_ATTR.get(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); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_DELETE_RDN_ATTR.get(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); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_DELETE_RDN_ATTR.get(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(ERR_MODIFY_INCREMENT_RDN.get( |
| | | 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); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_INCREMENT_REQUIRES_VALUE.get( |
| | | String.valueOf(entryDN), |
| | | a.getName())); |
| | | |
| | | break modifyProcessing; |
| | | } |
| | | else if (values.size() > 1) |
| | | { |
| | | localOp.setResultCode(ResultCode.PROTOCOL_ERROR); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_INCREMENT_REQUIRES_SINGLE_VALUE.get( |
| | | 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); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_INCREMENT_PROVIDED_VALUE_NOT_INTEGER.get( |
| | | 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); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_INCREMENT_REQUIRES_EXISTING_VALUE.get( |
| | | 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); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_INCREMENT_REQUIRES_INTEGER_VALUE.get( |
| | | 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); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_INCREMENT_REQUIRES_EXISTING_VALUE.get( |
| | | 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)) |
| | | { |
| | | pwpErrorType = PasswordPolicyErrorType.MUST_SUPPLY_OLD_PASSWORD; |
| | | |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_PW_CHANGE_REQUIRES_CURRENT_PW.get()); |
| | | break modifyProcessing; |
| | | } |
| | | |
| | | |
| | | // If this change would result in multiple password values, then see |
| | | // if that's OK. |
| | | if ((numPasswords > 1) && |
| | | (! pwPolicyState.getPolicy().allowMultiplePasswordValues())) |
| | | { |
| | | pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED; |
| | | |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_MULTIPLE_PASSWORDS_NOT_ALLOWED.get()); |
| | | 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) |
| | | { |
| | | MessageBuilder invalidReason = new MessageBuilder(); |
| | | if (! pwPolicyState.passwordIsAcceptable(localOp, modifiedEntry, |
| | | v.getValue(), |
| | | clearPasswords, |
| | | invalidReason)) |
| | | { |
| | | pwpErrorType = |
| | | PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY; |
| | | |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | |
| | | localOp.appendErrorMessage(ERR_MODIFY_PW_VALIDATION_FAILED |
| | | .get(invalidReason.toString())); |
| | | break modifyProcessing; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // If we should check the password history, then do so now. |
| | | if (pwPolicyState.maintainHistory()) |
| | | { |
| | | List<AttributeValue> newPasswords = localOp.getNewPasswords(); |
| | | if (newPasswords != null) |
| | | { |
| | | for (AttributeValue v : newPasswords) |
| | | { |
| | | if (pwPolicyState.isPasswordInHistory(v.getValue())) |
| | | { |
| | | if (selfChange || (! pwPolicyState.getPolicy(). |
| | | skipValidationForAdministrators())) |
| | | { |
| | | pwpErrorType = PasswordPolicyErrorType.PASSWORD_IN_HISTORY; |
| | | |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | |
| | | localOp.appendErrorMessage(ERR_MODIFY_PW_IN_HISTORY.get()); |
| | | break modifyProcessing; |
| | | } |
| | | } |
| | | } |
| | | |
| | | pwPolicyState.updatePasswordHistory(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // 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); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get( |
| | | 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 |
| | | { |
| | | if ((pwpErrorType == null) && |
| | | pwPolicyState.getPolicy().forceChangeOnReset()) |
| | | { |
| | | pwpErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET; |
| | | } |
| | | |
| | | pwPolicyState.setMustChangePassword( |
| | | pwPolicyState.getPolicy().forceChangeOnReset()); |
| | | } |
| | | } |
| | | |
| | | if (pwPolicyState.getPolicy().getRequireChangeByTime() > 0) |
| | | { |
| | | pwPolicyState.setRequiredChangeTime(); |
| | | } |
| | | modifications.addAll(pwPolicyState.getModifications()); |
| | | //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. |
| | | pwpErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET; |
| | | |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | |
| | | localOp.appendErrorMessage(ERR_MODIFY_MUST_CHANGE_PASSWORD.get()); |
| | | break modifyProcessing; |
| | | } |
| | | |
| | | // If the server is configured to check the schema and the |
| | | // operation is not a sycnhronization operation, |
| | | // make sure that the new entry is valid per the server schema. |
| | | if ((DirectoryServer.checkSchema()) && |
| | | (!localOp.isSynchronizationOperation()) ) |
| | | { |
| | | MessageBuilder invalidReason = new MessageBuilder(); |
| | | if (! modifiedEntry.conformsToSchema(null, false, false, false, |
| | | invalidReason)) |
| | | { |
| | | localOp.setResultCode(ResultCode.OBJECTCLASS_VIOLATION); |
| | | localOp.appendErrorMessage(ERR_MODIFY_VIOLATES_SCHEMA.get( |
| | | String.valueOf(entryDN), |
| | | invalidReason.toString())); |
| | | break modifyProcessing; |
| | | } |
| | | } |
| | | |
| | | |
| | | // Check for a request to cancel this operation. |
| | | if (localOp.getCancelRequest() != null) |
| | | { |
| | | 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); |
| | | |
| | | localOp.appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get()); |
| | | |
| | | localOp.setProcessingStopTime(); |
| | | |
| | | return; |
| | | } |
| | | else if (preOpResult.sendResponseImmediately()) |
| | | { |
| | | skipPostOperation = true; |
| | | break modifyProcessing; |
| | | } |
| | | else if (preOpResult.skipCoreProcessing()) |
| | | { |
| | | skipPostOperation = false; |
| | | break modifyProcessing; |
| | | } |
| | | } |
| | | |
| | | |
| | | // Check for a request to cancel this operation. |
| | | if (localOp.getCancelRequest() != null) |
| | | { |
| | | 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( |
| | | ERR_MODIFY_NO_BACKEND_FOR_ENTRY.get(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( |
| | | ERR_MODIFY_SERVER_READONLY.get(String.valueOf(entryDN))); |
| | | break modifyProcessing; |
| | | |
| | | case INTERNAL_ONLY: |
| | | if (! (localOp.isInternalOperation() || |
| | | localOp.isSynchronizationOperation())) |
| | | { |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_SERVER_READONLY.get(String.valueOf(entryDN))); |
| | | break modifyProcessing; |
| | | } |
| | | } |
| | | |
| | | switch (backend.getWritabilityMode()) |
| | | { |
| | | case DISABLED: |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_BACKEND_READONLY.get(String.valueOf(entryDN))); |
| | | break modifyProcessing; |
| | | |
| | | case INTERNAL_ONLY: |
| | | if (! localOp.isInternalOperation() || |
| | | localOp.isSynchronizationOperation()) |
| | | { |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | localOp.appendErrorMessage( |
| | | ERR_MODIFY_BACKEND_READONLY.get(String.valueOf(entryDN))); |
| | | break modifyProcessing; |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | if (noOp) |
| | | { |
| | | localOp.appendErrorMessage(INFO_MODIFY_NOOP.get()); |
| | | |
| | | localOp.setResultCode(ResultCode.NO_OPERATION); |
| | | } |
| | | 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(ERR_MODIFY_SYNCH_PREOP_FAILED. |
| | | get(localOp.getConnectionID(), localOp.getOperationID(), |
| | | getExceptionMessage(de))); |
| | | |
| | | localOp.setResponseData(de); |
| | | break modifyProcessing; |
| | | } |
| | | } |
| | | |
| | | backend.replaceEntry(modifiedEntry, localOp); |
| | | |
| | | |
| | | |
| | | // See if we need to generate any account status notifications as a |
| | | // result of the changes. |
| | | if (passwordChanged || enabledStateChanged || wasLocked) |
| | | { |
| | | handleAccountStatusNotifications(passwordChanged, selfChange, |
| | | enabledStateChanged, isEnabled, |
| | | wasLocked, localOp, |
| | | pwPolicyState, modifiedEntry); |
| | | } |
| | | } |
| | | |
| | | 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); |
| | | } |
| | | |
| | | if (! noOp) |
| | | { |
| | | localOp.setResultCode(ResultCode.SUCCESS); |
| | | } |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | localOp.setResultCode(de.getResultCode()); |
| | | localOp.appendErrorMessage(de.getMessageObject()); |
| | | 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()); |
| | | |
| | | Message message = coe.getMessageObject(); |
| | | 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(ERR_MODIFY_SYNCH_POSTOP_FAILED. |
| | | get(localOp.getConnectionID(), localOp.getOperationID(), |
| | | getExceptionMessage(de))); |
| | | |
| | | localOp.setResponseData(de); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // If the password policy request control was included, then make sure we |
| | | // send the corresponding response control. |
| | | if (pwPolicyControlRequested) |
| | | { |
| | | localOp.addResponseControl( |
| | | new PasswordPolicyResponseControl(null, 0, pwpErrorType)); |
| | | } |
| | | |
| | | |
| | | // Indicate that it is now too late to attempt to cancel the operation. |
| | | localOp.setCancelResult(CancelResult.TOO_LATE); |
| | | |
| | | // Invoke the post-operation or post-synchronization modify plugins. |
| | | if (localOp.isSynchronizationOperation()) |
| | | { |
| | | if (localOp.getResultCode() == ResultCode.SUCCESS) |
| | | { |
| | | pluginConfigManager.invokePostSynchronizationModifyPlugins(localOp); |
| | | } |
| | | } |
| | | else 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); |
| | | |
| | | localOp.appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get()); |
| | | |
| | | 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); |
| | | } |
| | | |
| | | Message message = ERR_MODIFY_ERROR_NOTIFYING_CHANGE_LISTENER.get( |
| | | getExceptionMessage(e)); |
| | | logError(message); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | // Stop the processing timer. |
| | | localOp.setProcessingStopTime(); |
| | | } |
| | | |
| | | /** |
| | | * Handles any account status notifications that may be needed as a result of |
| | | * modify processing using the provided information. |
| | | * |
| | | * @param passwordChanged Indicates whether the modify operation |
| | | * included a password change. |
| | | * @param selfChange Indicates whether the password change was |
| | | * performed by the end user or an administrator. |
| | | * @param enabledStateChanged Indicates whether the user's account changed |
| | | * from enabled to disabled (or vice versa) |
| | | * @param isEnabled Indicates whether the user's account is now |
| | | * enabled. |
| | | * @param wasLocked Indicates whether the user's account was |
| | | * previously locked. |
| | | * @param localOp The modify operation being processed. |
| | | * @param pwPolicyState The password policy state for the user. |
| | | * @param modifiedEntry The updated version of the entry. |
| | | */ |
| | | private void handleAccountStatusNotifications(boolean passwordChanged, |
| | | boolean selfChange, boolean enabledStateChanged, |
| | | boolean isEnabled, boolean wasLocked, |
| | | LocalBackendModifyOperation localOp, |
| | | PasswordPolicyState pwPolicyState, Entry modifiedEntry) |
| | | { |
| | | if (passwordChanged) |
| | | { |
| | | if (selfChange) |
| | | { |
| | | AuthenticationInfo authInfo = |
| | | localOp.getClientConnection().getAuthenticationInfo(); |
| | | if (authInfo.getAuthenticationDN().equals(modifiedEntry.getDN())) |
| | | { |
| | | localOp.getClientConnection().setMustChangePassword(false); |
| | | } |
| | | |
| | | Message message = INFO_MODIFY_PASSWORD_CHANGED.get(); |
| | | pwPolicyState.generateAccountStatusNotification( |
| | | AccountStatusNotificationType.PASSWORD_CHANGED, |
| | | modifiedEntry, message, |
| | | AccountStatusNotification.createProperties(pwPolicyState, false, -1, |
| | | localOp.getCurrentPasswords(), localOp.getNewPasswords())); |
| | | } |
| | | else |
| | | { |
| | | Message message = INFO_MODIFY_PASSWORD_RESET.get(); |
| | | pwPolicyState.generateAccountStatusNotification( |
| | | AccountStatusNotificationType.PASSWORD_RESET, modifiedEntry, |
| | | message, |
| | | AccountStatusNotification.createProperties(pwPolicyState, false, -1, |
| | | localOp.getCurrentPasswords(), localOp.getNewPasswords())); |
| | | } |
| | | } |
| | | |
| | | if (enabledStateChanged) |
| | | { |
| | | if (isEnabled) |
| | | { |
| | | Message message = INFO_MODIFY_ACCOUNT_ENABLED.get(); |
| | | pwPolicyState.generateAccountStatusNotification( |
| | | AccountStatusNotificationType.ACCOUNT_ENABLED, |
| | | modifiedEntry, message, |
| | | AccountStatusNotification.createProperties(pwPolicyState, false, -1, |
| | | null, null)); |
| | | } |
| | | else |
| | | { |
| | | Message message = INFO_MODIFY_ACCOUNT_DISABLED.get(); |
| | | pwPolicyState.generateAccountStatusNotification( |
| | | AccountStatusNotificationType.ACCOUNT_DISABLED, |
| | | modifiedEntry, message, |
| | | AccountStatusNotification.createProperties(pwPolicyState, false, -1, |
| | | null, null)); |
| | | } |
| | | } |
| | | |
| | | if (wasLocked) |
| | | { |
| | | Message message = INFO_MODIFY_ACCOUNT_UNLOCKED.get(); |
| | | pwPolicyState.generateAccountStatusNotification( |
| | | AccountStatusNotificationType.ACCOUNT_UNLOCKED, modifiedEntry, |
| | | message, |
| | | AccountStatusNotification.createProperties(pwPolicyState, false, -1, |
| | | null, null)); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 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 (!AccessControlConfigManager.getInstance(). |
| | | getAccessControlHandler(). |
| | | isAllowed(baseDN, localOp, c)) |
| | | { |
| | | localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid)); |
| | | skipPostOperation = true; |
| | | break searchProcessing; |
| | | } |
| | | |
| | | 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.getMessageObject()); |
| | | |
| | | 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()); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_SEARCH_CANNOT_GET_ENTRY_FOR_ASSERTION.get( |
| | | de.getMessageObject())); |
| | | |
| | | break searchProcessing; |
| | | } |
| | | |
| | | if (entry == null) |
| | | { |
| | | localOp.setResultCode(ResultCode.NO_SUCH_OBJECT); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_SEARCH_NO_SUCH_ENTRY_FOR_ASSERTION.get()); |
| | | |
| | | break searchProcessing; |
| | | } |
| | | |
| | | |
| | | if (! assertionFilter.matchesEntry(entry)) |
| | | { |
| | | localOp.setResultCode(ResultCode.ASSERTION_FAILED); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_SEARCH_ASSERTION_FAILED.get()); |
| | | |
| | | break searchProcessing; |
| | | } |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | localOp.setResultCode(ResultCode.PROTOCOL_ERROR); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_SEARCH_CANNOT_PROCESS_ASSERTION_FILTER.get( |
| | | de.getMessageObject())); |
| | | |
| | | 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)) |
| | | { |
| | | localOp.appendErrorMessage( |
| | | ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get()); |
| | | 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.getMessageObject()); |
| | | |
| | | 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.getMessageObject()); |
| | | |
| | | 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)) |
| | | { |
| | | localOp.appendErrorMessage( |
| | | ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get()); |
| | | 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.getMessageObject()); |
| | | |
| | | 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.getMessageObject()); |
| | | |
| | | 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.getMessageObject()); |
| | | |
| | | 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.getMessageObject()); |
| | | |
| | | 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); |
| | | } |
| | | // NYI -- Add support for additional controls. |
| | | else if (c.isCritical()) |
| | | { |
| | | if ((backend == null) || (! backend.supportsControl(oid))) |
| | | { |
| | | localOp.setResultCode( |
| | | ResultCode.UNAVAILABLE_CRITICAL_EXTENSION); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_SEARCH_UNSUPPORTED_CRITICAL_CONTROL.get(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); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_SEARCH_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get( |
| | | String.valueOf(baseDN))); |
| | | |
| | | skipPostOperation = true; |
| | | break searchProcessing; |
| | | } |
| | | |
| | | // Check for a request to cancel this operation. |
| | | if (localOp.getCancelRequest() != null) |
| | | { |
| | | 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); |
| | | |
| | | localOp.appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get()); |
| | | |
| | | localOp.setProcessingStopTime(); |
| | | return; |
| | | } |
| | | else if (preOpResult.sendResponseImmediately()) |
| | | { |
| | | skipPostOperation = true; |
| | | break searchProcessing; |
| | | } |
| | | else if (preOpResult.skipCoreProcessing()) |
| | | { |
| | | skipPostOperation = false; |
| | | break searchProcessing; |
| | | } |
| | | |
| | | |
| | | // Check for a request to cancel this operation. |
| | | if (localOp.getCancelRequest() != null) |
| | | { |
| | | 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( |
| | | ERR_SEARCH_BASE_DOESNT_EXIST.get(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.getMessageObject()); |
| | | 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()); |
| | | |
| | | Message message = coe.getMessageObject(); |
| | | 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()); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_SEARCH_BACKEND_EXCEPTION.get(getExceptionMessage(e))); |
| | | |
| | | if (persistentSearch != null) |
| | | { |
| | | DirectoryServer.deregisterPersistentSearch(persistentSearch); |
| | | localOp.setSendResponse(true); |
| | | } |
| | | |
| | | skipPostOperation = true; |
| | | break searchProcessing; |
| | | } |
| | | } |
| | | |
| | | |
| | | // Check for a request to cancel this operation. |
| | | if (localOp.getCancelRequest() != null) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | |
| | | // Invoke the post-operation search plugins. |
| | | if (! skipPostOperation) |
| | | { |
| | | PostOperationPluginResult postOperationResult = |
| | | pluginConfigManager.invokePostOperationSearchPlugins(localOp); |
| | | if (postOperationResult.connectionTerminated()) |
| | | { |
| | | localOp.setResultCode(ResultCode.CANCELED); |
| | | |
| | | localOp.appendErrorMessage(ERR_CANCELED_BY_POSTOP_DISCONNECT.get()); |
| | | |
| | | 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 localOp - 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(); |
| | | long idleTimeLimit = DirectoryServer.getIdleTimeLimit(); |
| | | 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); |
| | | |
| | | Message message = ERR_BIND_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get( |
| | | String.valueOf(bindDN)); |
| | | localOp.setAuthFailureReason(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 (!AccessControlConfigManager.getInstance(). |
| | | getAccessControlHandler(). isAllowed(bindDN, localOp, c)) |
| | | { |
| | | localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS); |
| | | localOp.appendErrorMessage(ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS |
| | | .get(oid)); |
| | | skipPostOperation = true; |
| | | break bindProcessing; |
| | | } |
| | | |
| | | 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); |
| | | |
| | | localOp.appendErrorMessage(ERR_BIND_UNSUPPORTED_CRITICAL_CONTROL |
| | | .get(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); |
| | | |
| | | Message message = ERR_BIND_REJECTED_LOCKDOWN_MODE.get(); |
| | | localOp.setAuthFailureReason(message); |
| | | |
| | | 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); |
| | | |
| | | Message message = ERR_BIND_DN_BUT_NO_PASSWORD.get(); |
| | | localOp.setAuthFailureReason(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); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_CANCELED_BY_PREOP_DISCONNECT.get()); |
| | | |
| | | 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) |
| | | { |
| | | Message message = ERR_BIND_OPERATION_CANNOT_LOCK_USER.get( |
| | | String.valueOf(bindDN)); |
| | | |
| | | localOp.setResultCode(DirectoryServer.getServerErrorResultCode()); |
| | | localOp.setAuthFailureReason(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.getMessageObject()); |
| | | |
| | | userEntry = null; |
| | | break bindProcessing; |
| | | } |
| | | |
| | | if (userEntry == null) |
| | | { |
| | | |
| | | Message message = ERR_BIND_OPERATION_UNKNOWN_USER.get( |
| | | String.valueOf(bindDN)); |
| | | |
| | | localOp.setResultCode(ResultCode.INVALID_CREDENTIALS); |
| | | localOp.setAuthFailureReason(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); |
| | | PasswordPolicy policy = pwPolicyState.getPolicy(); |
| | | AttributeType pwType = policy.getPasswordAttribute(); |
| | | |
| | | List<Attribute> pwAttr = userEntry.getAttribute(pwType); |
| | | if ((pwAttr == null) || (pwAttr.isEmpty())) |
| | | { |
| | | Message message = ERR_BIND_OPERATION_NO_PASSWORD.get( |
| | | String.valueOf(bindDN)); |
| | | |
| | | localOp.setResultCode(ResultCode.INVALID_CREDENTIALS); |
| | | localOp.setAuthFailureReason(message); |
| | | break bindProcessing; |
| | | } |
| | | |
| | | |
| | | // If the password policy is configured to track authentication |
| | | // failures or keep the last login time and the associated backend |
| | | // is disabled, then we may need to reject the bind immediately. |
| | | if ((policy.getStateUpdateFailurePolicy() == |
| | | PasswordPolicyCfgDefn.StateUpdateFailurePolicy.PROACTIVE) && |
| | | ((policy.getLockoutFailureCount() > 0) || |
| | | ((policy.getLastLoginTimeAttribute() != null) && |
| | | (policy.getLastLoginTimeFormat() != null))) && |
| | | ((DirectoryServer.getWritabilityMode() == |
| | | WritabilityMode.DISABLED) || |
| | | (backend.getWritabilityMode() == WritabilityMode.DISABLED))) |
| | | { |
| | | // This policy isn't applicable to root users, so if it's a root |
| | | // user then ignore it. |
| | | if (! DirectoryServer.isRootDN(bindDN)) |
| | | { |
| | | Message message = ERR_BIND_OPERATION_WRITABILITY_DISABLED.get( |
| | | String.valueOf(bindDN)); |
| | | |
| | | localOp.setResultCode(ResultCode.INVALID_CREDENTIALS); |
| | | localOp.setAuthFailureReason(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 (policy.requireSecureAuthentication() && |
| | | (! clientConnection.isSecure())) |
| | | { |
| | | Message message = ERR_BIND_OPERATION_INSECURE_SIMPLE_BIND.get( |
| | | String.valueOf(bindDN)); |
| | | |
| | | localOp.setResultCode(ResultCode.INVALID_CREDENTIALS); |
| | | localOp.setAuthFailureReason(message); |
| | | break bindProcessing; |
| | | } |
| | | |
| | | |
| | | // Check to see if the user is administratively disabled or locked. |
| | | if (pwPolicyState.isDisabled()) |
| | | { |
| | | Message message = ERR_BIND_OPERATION_ACCOUNT_DISABLED.get( |
| | | String.valueOf(bindDN)); |
| | | |
| | | localOp.setResultCode(ResultCode.INVALID_CREDENTIALS); |
| | | localOp.setAuthFailureReason(message); |
| | | break bindProcessing; |
| | | } |
| | | else if (pwPolicyState.isAccountExpired()) |
| | | { |
| | | Message message = ERR_BIND_OPERATION_ACCOUNT_EXPIRED.get( |
| | | String.valueOf(bindDN)); |
| | | |
| | | localOp.setResultCode(ResultCode.INVALID_CREDENTIALS); |
| | | localOp.setAuthFailureReason(message); |
| | | |
| | | pwPolicyState.generateAccountStatusNotification( |
| | | AccountStatusNotificationType.ACCOUNT_EXPIRED, userEntry, |
| | | message, |
| | | AccountStatusNotification.createProperties(pwPolicyState, |
| | | false, -1, null, null)); |
| | | |
| | | break bindProcessing; |
| | | } |
| | | else if (pwPolicyState.lockedDueToFailures()) |
| | | { |
| | | Message message = ERR_BIND_OPERATION_ACCOUNT_FAILURE_LOCKED.get( |
| | | String.valueOf(bindDN)); |
| | | |
| | | if (pwPolicyErrorType == null) |
| | | { |
| | | pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED; |
| | | } |
| | | |
| | | localOp.setResultCode(ResultCode.INVALID_CREDENTIALS); |
| | | localOp.setAuthFailureReason(message); |
| | | break bindProcessing; |
| | | } |
| | | else if (pwPolicyState.lockedDueToMaximumResetAge()) |
| | | { |
| | | Message message = ERR_BIND_OPERATION_ACCOUNT_RESET_LOCKED.get( |
| | | String.valueOf(bindDN)); |
| | | |
| | | if (pwPolicyErrorType == null) |
| | | { |
| | | pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED; |
| | | } |
| | | |
| | | localOp.setResultCode(ResultCode.INVALID_CREDENTIALS); |
| | | localOp.setAuthFailureReason(message); |
| | | |
| | | pwPolicyState.generateAccountStatusNotification( |
| | | AccountStatusNotificationType.ACCOUNT_RESET_LOCKED, |
| | | userEntry, message, |
| | | AccountStatusNotification.createProperties(pwPolicyState, |
| | | false, -1, null, null)); |
| | | |
| | | break bindProcessing; |
| | | } |
| | | else if (pwPolicyState.lockedDueToIdleInterval()) |
| | | { |
| | | Message message = ERR_BIND_OPERATION_ACCOUNT_IDLE_LOCKED.get( |
| | | String.valueOf(bindDN)); |
| | | |
| | | if (pwPolicyErrorType == null) |
| | | { |
| | | pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED; |
| | | } |
| | | |
| | | localOp.setResultCode(ResultCode.INVALID_CREDENTIALS); |
| | | localOp.setAuthFailureReason(message); |
| | | |
| | | pwPolicyState.generateAccountStatusNotification( |
| | | AccountStatusNotificationType.ACCOUNT_IDLE_LOCKED, userEntry, |
| | | message, |
| | | AccountStatusNotification.createProperties(pwPolicyState, |
| | | false, -1, null, null)); |
| | | |
| | | 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 = policy.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 |
| | | { |
| | | Message message = ERR_BIND_OPERATION_PASSWORD_EXPIRED.get( |
| | | String.valueOf(bindDN)); |
| | | |
| | | localOp.setResultCode(ResultCode.INVALID_CREDENTIALS); |
| | | localOp.setAuthFailureReason(message); |
| | | |
| | | pwPolicyState.generateAccountStatusNotification( |
| | | AccountStatusNotificationType.PASSWORD_EXPIRED, |
| | | userEntry, message, |
| | | AccountStatusNotification.createProperties(pwPolicyState, |
| | | false, -1, null, null)); |
| | | |
| | | break bindProcessing; |
| | | } |
| | | } |
| | | else |
| | | { |
| | | Message message = ERR_BIND_OPERATION_PASSWORD_EXPIRED.get( |
| | | String.valueOf(bindDN)); |
| | | |
| | | localOp.setResultCode(ResultCode.INVALID_CREDENTIALS); |
| | | localOp.setAuthFailureReason(message); |
| | | |
| | | pwPolicyState.generateAccountStatusNotification( |
| | | AccountStatusNotificationType.PASSWORD_EXPIRED, userEntry, |
| | | message, |
| | | AccountStatusNotification.createProperties(pwPolicyState, |
| | | false, -1, null, null)); |
| | | |
| | | break bindProcessing; |
| | | } |
| | | } |
| | | else if (pwPolicyState.shouldWarn()) |
| | | { |
| | | int numSeconds = pwPolicyState.getSecondsUntilExpiration(); |
| | | |
| | | 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); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_CANCELED_BY_PREOP_DISCONNECT.get()); |
| | | |
| | | 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); |
| | | |
| | | Message message = ERR_BIND_REJECTED_LOCKDOWN_MODE.get(); |
| | | localOp.setAuthFailureReason(message); |
| | | |
| | | 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()) |
| | | { |
| | | Message message = WARN_BIND_MULTIPLE_USER_SIZE_LIMITS.get( |
| | | String.valueOf(userEntry.getDN())); |
| | | logError(message); |
| | | } |
| | | else |
| | | { |
| | | try |
| | | { |
| | | sizeLimit = Integer.parseInt(v.getStringValue()); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | |
| | | Message message = WARN_BIND_CANNOT_PROCESS_USER_SIZE_LIMIT |
| | | .get(v.getStringValue(), |
| | | String.valueOf(userEntry.getDN())); |
| | | logError(message); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // 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()) |
| | | { |
| | | Message message = WARN_BIND_MULTIPLE_USER_TIME_LIMITS.get( |
| | | String.valueOf(userEntry.getDN())); |
| | | logError(message); |
| | | } |
| | | else |
| | | { |
| | | try |
| | | { |
| | | timeLimit = Integer.parseInt(v.getStringValue()); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | |
| | | Message message = |
| | | WARN_BIND_CANNOT_PROCESS_USER_TIME_LIMIT. |
| | | get(v.getStringValue(), |
| | | String.valueOf(userEntry.getDN())); |
| | | logError(message); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // See if the user's entry contains a custom idle time limit. |
| | | attrType = DirectoryServer.getAttributeType( |
| | | OP_ATTR_USER_IDLE_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()) |
| | | { |
| | | Message message = WARN_BIND_MULTIPLE_USER_IDLE_TIME_LIMITS. |
| | | get(String.valueOf(userEntry.getDN())); |
| | | logError(message); |
| | | } |
| | | else |
| | | { |
| | | try |
| | | { |
| | | idleTimeLimit = |
| | | 1000L * Long.parseLong(v.getStringValue()); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | |
| | | Message message = |
| | | WARN_BIND_CANNOT_PROCESS_USER_IDLE_TIME_LIMIT. |
| | | get(v.getStringValue(), |
| | | String.valueOf(userEntry.getDN())); |
| | | logError(message); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // 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()) |
| | | { |
| | | Message message = |
| | | WARN_BIND_MULTIPLE_USER_LOOKTHROUGH_LIMITS. |
| | | get(String.valueOf(userEntry.getDN())); |
| | | logError(message); |
| | | } |
| | | else |
| | | { |
| | | try |
| | | { |
| | | lookthroughLimit = Integer.parseInt(v.getStringValue()); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | |
| | | Message message = |
| | | WARN_BIND_CANNOT_PROCESS_USER_LOOKTHROUGH_LIMIT. |
| | | get(v.getStringValue(), |
| | | String.valueOf(userEntry.getDN())); |
| | | logError(message); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | pwPolicyState.handleDeprecatedStorageSchemes(simplePassword); |
| | | pwPolicyState.clearFailureLockout(); |
| | | |
| | | if (isFirstWarning) |
| | | { |
| | | pwPolicyState.setWarnedTime(); |
| | | |
| | | int numSeconds = pwPolicyState.getSecondsUntilExpiration(); |
| | | Message timeToExpiration = secondsToTimeString(numSeconds); |
| | | |
| | | Message message = WARN_BIND_PASSWORD_EXPIRING.get( |
| | | timeToExpiration); |
| | | |
| | | pwPolicyState.generateAccountStatusNotification( |
| | | AccountStatusNotificationType.PASSWORD_EXPIRING, userEntry, |
| | | message, |
| | | AccountStatusNotification.createProperties(pwPolicyState, |
| | | false, numSeconds, null, null)); |
| | | } |
| | | |
| | | if (isGraceLogin) |
| | | { |
| | | pwPolicyState.updateGraceLoginTimes(); |
| | | } |
| | | |
| | | pwPolicyState.setLastLoginTime(); |
| | | } |
| | | else |
| | | { |
| | | Message message = ERR_BIND_OPERATION_WRONG_PASSWORD.get(); |
| | | |
| | | localOp.setResultCode(ResultCode.INVALID_CREDENTIALS); |
| | | localOp.setAuthFailureReason(message); |
| | | |
| | | if (policy.getLockoutFailureCount() > 0) |
| | | { |
| | | pwPolicyState.updateAuthFailureTimes(); |
| | | if (pwPolicyState.lockedDueToFailures()) |
| | | { |
| | | AccountStatusNotificationType notificationType; |
| | | |
| | | boolean tempLocked; |
| | | int lockoutDuration = pwPolicyState.getSecondsUntilUnlock(); |
| | | if (lockoutDuration > -1) |
| | | { |
| | | notificationType = AccountStatusNotificationType. |
| | | ACCOUNT_TEMPORARILY_LOCKED; |
| | | tempLocked = true; |
| | | |
| | | message = ERR_BIND_ACCOUNT_TEMPORARILY_LOCKED.get( |
| | | secondsToTimeString(lockoutDuration)); |
| | | } |
| | | else |
| | | { |
| | | notificationType = AccountStatusNotificationType. |
| | | ACCOUNT_PERMANENTLY_LOCKED; |
| | | tempLocked = false; |
| | | |
| | | message = ERR_BIND_ACCOUNT_PERMANENTLY_LOCKED.get(); |
| | | } |
| | | |
| | | pwPolicyState.generateAccountStatusNotification( |
| | | notificationType, userEntry, message, |
| | | AccountStatusNotification.createProperties(pwPolicyState, |
| | | tempLocked, -1, null, null)); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | |
| | | Message message = ERR_BIND_OPERATION_PASSWORD_VALIDATION_EXCEPTION |
| | | .get(getExceptionMessage(e)); |
| | | |
| | | localOp.setResultCode(DirectoryServer.getServerErrorResultCode()); |
| | | localOp.setAuthFailureReason(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); |
| | | |
| | | Message message = ERR_BIND_OPERATION_UNKNOWN_SASL_MECHANISM.get( |
| | | saslMechanism); |
| | | |
| | | localOp.appendErrorMessage(message); |
| | | localOp.setAuthFailureReason(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); |
| | | |
| | | localOp.appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get()); |
| | | |
| | | 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); |
| | | |
| | | Message message = ERR_BIND_REJECTED_LOCKDOWN_MODE.get(); |
| | | localOp.setAuthFailureReason(message); |
| | | |
| | | 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) |
| | | { |
| | | PasswordPolicy policy = pwPolicyState.getPolicy(); |
| | | |
| | | // If the password policy is configured to track authentication |
| | | // failures or keep the last login time and the associated backend |
| | | // is disabled, then we may need to reject the bind immediately. |
| | | if ((policy.getStateUpdateFailurePolicy() == |
| | | PasswordPolicyCfgDefn.StateUpdateFailurePolicy.PROACTIVE) && |
| | | ((policy.getLockoutFailureCount() > 0) || |
| | | ((policy.getLastLoginTimeAttribute() != null) && |
| | | (policy.getLastLoginTimeFormat() != null))) && |
| | | ((DirectoryServer.getWritabilityMode() == |
| | | WritabilityMode.DISABLED) || |
| | | (backend.getWritabilityMode() == WritabilityMode.DISABLED))) |
| | | { |
| | | // This policy isn't applicable to root users, so if it's a root |
| | | // user then ignore it. |
| | | if (! DirectoryServer.isRootDN(bindDN)) |
| | | { |
| | | Message message = ERR_BIND_OPERATION_WRITABILITY_DISABLED.get( |
| | | userDNString); |
| | | |
| | | localOp.setResultCode(ResultCode.INVALID_CREDENTIALS); |
| | | localOp.setAuthFailureReason(message); |
| | | break bindProcessing; |
| | | } |
| | | } |
| | | else if (pwPolicyState.isDisabled()) |
| | | { |
| | | localOp.setResultCode(ResultCode.INVALID_CREDENTIALS); |
| | | |
| | | Message message = ERR_BIND_OPERATION_ACCOUNT_DISABLED.get( |
| | | userDNString); |
| | | localOp.setAuthFailureReason(message); |
| | | break bindProcessing; |
| | | } |
| | | else if (pwPolicyState.isAccountExpired()) |
| | | { |
| | | localOp.setResultCode(ResultCode.INVALID_CREDENTIALS); |
| | | |
| | | Message message = ERR_BIND_OPERATION_ACCOUNT_EXPIRED.get( |
| | | userDNString); |
| | | localOp.setAuthFailureReason(message); |
| | | |
| | | pwPolicyState.generateAccountStatusNotification( |
| | | AccountStatusNotificationType.ACCOUNT_EXPIRED, |
| | | saslAuthUserEntry, message, |
| | | AccountStatusNotification.createProperties(pwPolicyState, |
| | | false, -1, null, null)); |
| | | |
| | | break bindProcessing; |
| | | } |
| | | |
| | | if (policy.requireSecureAuthentication() && |
| | | (! clientConnection.isSecure()) && |
| | | (! saslHandler.isSecure(saslMechanism))) |
| | | { |
| | | localOp.setResultCode(ResultCode.INVALID_CREDENTIALS); |
| | | |
| | | Message message = ERR_BIND_OPERATION_INSECURE_SASL_BIND.get( |
| | | saslMechanism, userDNString); |
| | | localOp.setAuthFailureReason(message); |
| | | break bindProcessing; |
| | | } |
| | | |
| | | if (pwPolicyState.lockedDueToFailures()) |
| | | { |
| | | localOp.setResultCode(ResultCode.INVALID_CREDENTIALS); |
| | | |
| | | if (pwPolicyErrorType == null) |
| | | { |
| | | pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED; |
| | | } |
| | | |
| | | Message message = ERR_BIND_OPERATION_ACCOUNT_FAILURE_LOCKED.get( |
| | | userDNString); |
| | | localOp.setAuthFailureReason(message); |
| | | break bindProcessing; |
| | | } |
| | | |
| | | if (pwPolicyState.lockedDueToIdleInterval()) |
| | | { |
| | | localOp.setResultCode(ResultCode.INVALID_CREDENTIALS); |
| | | |
| | | if (pwPolicyErrorType == null) |
| | | { |
| | | pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED; |
| | | } |
| | | |
| | | Message message = ERR_BIND_OPERATION_ACCOUNT_IDLE_LOCKED.get( |
| | | userDNString); |
| | | localOp.setAuthFailureReason(message); |
| | | |
| | | pwPolicyState.generateAccountStatusNotification( |
| | | AccountStatusNotificationType.ACCOUNT_IDLE_LOCKED, |
| | | saslAuthUserEntry, message, |
| | | AccountStatusNotification.createProperties(pwPolicyState, |
| | | false, -1, null, null)); |
| | | |
| | | break bindProcessing; |
| | | } |
| | | |
| | | |
| | | if (saslHandler.isPasswordBased(saslMechanism)) |
| | | { |
| | | if (pwPolicyState.lockedDueToMaximumResetAge()) |
| | | { |
| | | localOp.setResultCode(ResultCode.INVALID_CREDENTIALS); |
| | | |
| | | if (pwPolicyErrorType == null) |
| | | { |
| | | pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED; |
| | | } |
| | | |
| | | Message message = ERR_BIND_OPERATION_ACCOUNT_RESET_LOCKED.get( |
| | | userDNString); |
| | | localOp.setAuthFailureReason(message); |
| | | |
| | | pwPolicyState.generateAccountStatusNotification( |
| | | AccountStatusNotificationType.ACCOUNT_RESET_LOCKED, |
| | | saslAuthUserEntry, message, |
| | | AccountStatusNotification.createProperties(pwPolicyState, |
| | | false, -1, null, null)); |
| | | |
| | | break bindProcessing; |
| | | } |
| | | |
| | | if (pwPolicyState.isPasswordExpired()) |
| | | { |
| | | if (pwPolicyErrorType == null) |
| | | { |
| | | pwPolicyErrorType = PasswordPolicyErrorType.PASSWORD_EXPIRED; |
| | | } |
| | | |
| | | int maxGraceLogins = policy.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 |
| | | { |
| | | Message message = ERR_BIND_OPERATION_PASSWORD_EXPIRED.get( |
| | | String.valueOf(bindDN)); |
| | | |
| | | localOp.setResultCode(ResultCode.INVALID_CREDENTIALS); |
| | | localOp.setAuthFailureReason(message); |
| | | |
| | | pwPolicyState.generateAccountStatusNotification( |
| | | AccountStatusNotificationType.PASSWORD_EXPIRED, |
| | | saslAuthUserEntry, message, |
| | | AccountStatusNotification.createProperties( |
| | | pwPolicyState, false, -1, null, null)); |
| | | |
| | | break bindProcessing; |
| | | } |
| | | } |
| | | else |
| | | { |
| | | Message message = ERR_BIND_OPERATION_PASSWORD_EXPIRED.get( |
| | | String.valueOf(bindDN)); |
| | | |
| | | localOp.setResultCode(ResultCode.INVALID_CREDENTIALS); |
| | | localOp.setAuthFailureReason(message); |
| | | |
| | | pwPolicyState.generateAccountStatusNotification( |
| | | AccountStatusNotificationType.PASSWORD_EXPIRED, |
| | | saslAuthUserEntry, message, |
| | | AccountStatusNotification.createProperties(pwPolicyState, |
| | | false, -1, null, null)); |
| | | |
| | | break bindProcessing; |
| | | } |
| | | } |
| | | else if (pwPolicyState.shouldWarn()) |
| | | { |
| | | int numSeconds = pwPolicyState.getSecondsUntilExpiration(); |
| | | |
| | | 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(); |
| | | Message timeToExpiration = secondsToTimeString(numSeconds); |
| | | |
| | | Message message = WARN_BIND_PASSWORD_EXPIRING.get( |
| | | timeToExpiration); |
| | | |
| | | pwPolicyState.generateAccountStatusNotification( |
| | | AccountStatusNotificationType.PASSWORD_EXPIRING, |
| | | saslAuthUserEntry, message, |
| | | AccountStatusNotification.createProperties(pwPolicyState, |
| | | false, numSeconds, null, null)); |
| | | } |
| | | |
| | | 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()) |
| | | { |
| | | Message message = |
| | | WARN_BIND_MULTIPLE_USER_SIZE_LIMITS.get(userDNString); |
| | | logError(message); |
| | | } |
| | | else |
| | | { |
| | | try |
| | | { |
| | | sizeLimit = Integer.parseInt(v.getStringValue()); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | |
| | | Message message = |
| | | WARN_BIND_CANNOT_PROCESS_USER_SIZE_LIMIT. |
| | | get(v.getStringValue(), userDNString); |
| | | logError(message); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // 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()) |
| | | { |
| | | Message message = |
| | | WARN_BIND_MULTIPLE_USER_TIME_LIMITS.get(userDNString); |
| | | logError(message); |
| | | } |
| | | else |
| | | { |
| | | try |
| | | { |
| | | timeLimit = Integer.parseInt(v.getStringValue()); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | |
| | | Message message = |
| | | WARN_BIND_CANNOT_PROCESS_USER_TIME_LIMIT. |
| | | get(v.getStringValue(), userDNString); |
| | | logError(message); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // See if the user's entry contains a custom idle time limit. |
| | | attrType = DirectoryServer.getAttributeType( |
| | | OP_ATTR_USER_IDLE_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()) |
| | | { |
| | | Message message = WARN_BIND_MULTIPLE_USER_IDLE_TIME_LIMITS. |
| | | get(String.valueOf(userDNString)); |
| | | logError(message); |
| | | } |
| | | else |
| | | { |
| | | try |
| | | { |
| | | idleTimeLimit = |
| | | 1000L * Long.parseLong(v.getStringValue()); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | |
| | | Message message = |
| | | WARN_BIND_CANNOT_PROCESS_USER_IDLE_TIME_LIMIT. |
| | | get(v.getStringValue(), |
| | | String.valueOf(userDNString)); |
| | | logError(message); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // 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()) |
| | | { |
| | | Message message = |
| | | WARN_BIND_MULTIPLE_USER_LOOKTHROUGH_LIMITS. |
| | | get(userDNString); |
| | | logError(message); |
| | | } |
| | | else |
| | | { |
| | | try |
| | | { |
| | | lookthroughLimit = Integer.parseInt(v.getStringValue()); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | |
| | | Message message = |
| | | WARN_BIND_CANNOT_PROCESS_USER_LOOKTHROUGH_LIMIT. |
| | | get(v.getStringValue(), userDNString); |
| | | logError(message); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | 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; |
| | | boolean tempLocked; |
| | | Message message; |
| | | |
| | | int lockoutDuration = pwPolicyState.getSecondsUntilUnlock(); |
| | | if (lockoutDuration > -1) |
| | | { |
| | | notificationType = AccountStatusNotificationType. |
| | | ACCOUNT_TEMPORARILY_LOCKED; |
| | | tempLocked = true; |
| | | message = ERR_BIND_ACCOUNT_TEMPORARILY_LOCKED.get( |
| | | secondsToTimeString(lockoutDuration)); |
| | | } |
| | | else |
| | | { |
| | | notificationType = AccountStatusNotificationType. |
| | | ACCOUNT_PERMANENTLY_LOCKED; |
| | | tempLocked = false; |
| | | message = ERR_BIND_ACCOUNT_PERMANENTLY_LOCKED.get(); |
| | | } |
| | | |
| | | pwPolicyState.generateAccountStatusNotification( |
| | | notificationType, saslAuthUserEntry, message, |
| | | AccountStatusNotification.createProperties( |
| | | pwPolicyState, tempLocked, -1, null, null)); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | 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); |
| | | |
| | | localOp.appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get()); |
| | | |
| | | 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.setIdleTimeLimit(idleTimeLimit); |
| | | 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 localOp - 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 a request to cancel this operation. |
| | | if (localOp.getCancelRequest() != null) |
| | | { |
| | | 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 a request to cancel this operation. |
| | | if (localOp.getCancelRequest() != null) |
| | | { |
| | | 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(ERR_ADD_CANNOT_ADD_ROOT_DSE.get()); |
| | | 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(ERR_ADD_ENTRY_NOT_SUFFIX.get( |
| | | 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(ERR_ADD_CANNOT_LOCK_PARENT.get( |
| | | String.valueOf(entryDN), |
| | | String.valueOf(parentDN))); |
| | | |
| | | skipPostOperation = true; |
| | | break addProcessing; |
| | | } |
| | | } |
| | | |
| | | |
| | | try |
| | | { |
| | | // Check for a request to cancel this operation. |
| | | if (localOp.getCancelRequest() != null) |
| | | { |
| | | 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(ERR_ADD_CANNOT_LOCK_ENTRY.get( |
| | | String.valueOf(entryDN))); |
| | | |
| | | skipPostOperation = true; |
| | | break addProcessing; |
| | | } |
| | | |
| | | |
| | | // Invoke any conflict resolution processing that might be needed by the |
| | | // synchronization provider. |
| | | for (SynchronizationProvider provider : |
| | | DirectoryServer.getSynchronizationProviders()) |
| | | { |
| | | try |
| | | { |
| | | SynchronizationProviderResult result = |
| | | provider.handleConflictResolution(localOp); |
| | | if (! result.continueOperationProcessing()) |
| | | { |
| | | break addProcessing; |
| | | } |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | logError(ERR_ADD_SYNCH_CONFLICT_RESOLUTION_FAILED. |
| | | get(localOp.getConnectionID(), localOp.getOperationID(), |
| | | getExceptionMessage(de))); |
| | | |
| | | localOp.setResponseData(de); |
| | | break addProcessing; |
| | | } |
| | | } |
| | | |
| | | // If the attribute type is marked "NO-USER-MODIFICATION" then fail |
| | | // unless this is an internal operation or is related to |
| | | // synchronization in some way. |
| | | // This must be done before running the password policy code |
| | | // and any other code that may add attributes marked as |
| | | // "NO-USER-MODIFICATION" |
| | | // |
| | | // Note that doing this checks at this time |
| | | // of the processing does not make it possible for pre-parse plugins |
| | | // to add NO-USER-MODIFICATION attributes to the entry. |
| | | for (AttributeType at : userAttributes.keySet()) |
| | | { |
| | | if (at.isNoUserModification()) |
| | | { |
| | | if (! (localOp.isInternalOperation() || |
| | | localOp.isSynchronizationOperation())) |
| | | { |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | localOp.appendErrorMessage(ERR_ADD_ATTR_IS_NO_USER_MOD.get( |
| | | String.valueOf(entryDN), at.getNameOrOID())); |
| | | |
| | | break addProcessing; |
| | | } |
| | | } |
| | | } |
| | | |
| | | for (AttributeType at : operationalAttributes.keySet()) |
| | | { |
| | | if (at.isNoUserModification()) |
| | | { |
| | | if (! (localOp.isInternalOperation() || |
| | | localOp.isSynchronizationOperation())) |
| | | { |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | localOp.appendErrorMessage(ERR_ADD_ATTR_IS_NO_USER_MOD.get( |
| | | String.valueOf(entryDN), at.getNameOrOID())); |
| | | |
| | | break addProcessing; |
| | | } |
| | | } |
| | | } |
| | | |
| | | // Check to see if the entry already exists. We do this before |
| | | // checking whether the parent exists to ensure a referral entry |
| | | // above the parent results in a correct referral. |
| | | try |
| | | { |
| | | if (DirectoryServer.entryExists(entryDN)) |
| | | { |
| | | localOp.setResultCode(ResultCode.ENTRY_ALREADY_EXISTS); |
| | | localOp.appendErrorMessage(ERR_ADD_ENTRY_ALREADY_EXISTS.get( |
| | | String.valueOf(entryDN))); |
| | | break addProcessing; |
| | | } |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | localOp.setResultCode(de.getResultCode()); |
| | | localOp.appendErrorMessage(de.getMessageObject()); |
| | | 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(ERR_ADD_NO_PARENT.get( |
| | | 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.getMessageObject()); |
| | | 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.UNWILLING_TO_PERFORM); |
| | | |
| | | localOp.appendErrorMessage(ERR_ADD_MISSING_RDN_ATTRIBUTE.get( |
| | | 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.UNWILLING_TO_PERFORM); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_ADD_MISSING_RDN_ATTRIBUTE.get( |
| | | 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.UNWILLING_TO_PERFORM); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_ADD_MISSING_RDN_ATTRIBUTE.get( |
| | | 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.UNWILLING_TO_PERFORM); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_ADD_MISSING_RDN_ATTRIBUTE.get( |
| | | 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))) |
| | | { |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_ADD_CHANGE_PRIVILEGE_INSUFFICIENT_PRIVILEGES.get()); |
| | | 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); |
| | | } |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_ADD_INVALID_PWPOLICY_DN_SYNTAX.get( |
| | | String.valueOf(entryDN), |
| | | de.getMessageObject())); |
| | | |
| | | localOp.setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX); |
| | | break addProcessing; |
| | | } |
| | | |
| | | pwPolicy = DirectoryServer.getPasswordPolicy(policyDN); |
| | | if (pwPolicy == null) |
| | | { |
| | | localOp.appendErrorMessage( |
| | | ERR_ADD_NO_SUCH_PWPOLICY.get(String.valueOf(entryDN), |
| | | String.valueOf(policyDN))); |
| | | |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | 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; |
| | | } |
| | | } |
| | | |
| | | // If the server is configured to check schema and the |
| | | // operation is not a synchronization operation, |
| | | // check to see if the entry is valid according to the server schema, |
| | | // and also whether its attributes are valid according to their syntax. |
| | | if ((DirectoryServer.checkSchema()) && |
| | | (!localOp.isSynchronizationOperation()) ) |
| | | { |
| | | MessageBuilder invalidReason = new MessageBuilder(); |
| | | 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 MessageBuilder(); |
| | | for (List<Attribute> attrList : userAttributes.values()) |
| | | { |
| | | for (Attribute a : attrList) |
| | | { |
| | | AttributeSyntax syntax = a.getAttributeType().getSyntax(); |
| | | if (syntax != null) |
| | | { |
| | | for (AttributeValue v : a.getValues()) |
| | | { |
| | | if (! syntax.valueIsAcceptable(v.getValue(), |
| | | invalidReason)) |
| | | { |
| | | Message message = WARN_ADD_OP_INVALID_SYNTAX. |
| | | get(String.valueOf(entryDN), |
| | | String.valueOf(v.getStringValue()), |
| | | String.valueOf(a.getName()), |
| | | String.valueOf(invalidReason)); |
| | | invalidReason = new MessageBuilder(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)) |
| | | { |
| | | Message message = WARN_ADD_OP_INVALID_SYNTAX. |
| | | get(String.valueOf(entryDN), |
| | | String.valueOf(v.getStringValue()), |
| | | String.valueOf(a.getName()), |
| | | String.valueOf(invalidReason)); |
| | | invalidReason = new MessageBuilder(message); |
| | | |
| | | localOp.setResultCode( |
| | | ResultCode.INVALID_ATTRIBUTE_SYNTAX); |
| | | localOp.setErrorMessage(invalidReason); |
| | | break addProcessing; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | break; |
| | | |
| | | |
| | | case WARN: |
| | | invalidReason = new MessageBuilder(); |
| | | for (List<Attribute> attrList : userAttributes.values()) |
| | | { |
| | | for (Attribute a : attrList) |
| | | { |
| | | AttributeSyntax syntax = a.getAttributeType().getSyntax(); |
| | | if (syntax != null) |
| | | { |
| | | for (AttributeValue v : a.getValues()) |
| | | { |
| | | if (! syntax.valueIsAcceptable(v.getValue(), |
| | | invalidReason)) |
| | | { |
| | | logError(WARN_ADD_OP_INVALID_SYNTAX. |
| | | get(String.valueOf(entryDN), |
| | | String.valueOf(v.getStringValue()), |
| | | String.valueOf(a.getName()), |
| | | String.valueOf(invalidReason))); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | for (List<Attribute> attrList : operationalAttributes.values()) |
| | | { |
| | | for (Attribute a : attrList) |
| | | { |
| | | AttributeSyntax syntax = a.getAttributeType().getSyntax(); |
| | | if (syntax != null) |
| | | { |
| | | for (AttributeValue v : a.getValues()) |
| | | { |
| | | if (! syntax.valueIsAcceptable(v.getValue(), |
| | | invalidReason)) |
| | | { |
| | | logError(WARN_ADD_OP_INVALID_SYNTAX. |
| | | get(String.valueOf(entryDN), |
| | | String.valueOf(v.getStringValue()), |
| | | String.valueOf(a.getName()), |
| | | String.valueOf(invalidReason))); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | break; |
| | | } |
| | | } |
| | | |
| | | |
| | | // See if the entry contains any attributes or object classes marked |
| | | // OBSOLETE. If so, then reject the entry. |
| | | for (AttributeType at : userAttributes.keySet()) |
| | | { |
| | | if (at.isObsolete()) |
| | | { |
| | | Message message = WARN_ADD_ATTR_IS_OBSOLETE.get( |
| | | String.valueOf(entryDN), |
| | | at.getNameOrOID()); |
| | | localOp.appendErrorMessage(message); |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | break addProcessing; |
| | | } |
| | | } |
| | | |
| | | for (AttributeType at : operationalAttributes.keySet()) |
| | | { |
| | | if (at.isObsolete()) |
| | | { |
| | | Message message = WARN_ADD_ATTR_IS_OBSOLETE.get( |
| | | String.valueOf(entryDN), |
| | | at.getNameOrOID()); |
| | | localOp.appendErrorMessage(message); |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | break addProcessing; |
| | | } |
| | | } |
| | | |
| | | for (ObjectClass oc : objectClasses.keySet()) |
| | | { |
| | | if (oc.isObsolete()) |
| | | { |
| | | Message message = WARN_ADD_OC_IS_OBSOLETE.get( |
| | | String.valueOf(entryDN), |
| | | oc.getNameOrOID()); |
| | | localOp.appendErrorMessage(message); |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | 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 (!AccessControlConfigManager.getInstance(). |
| | | getAccessControlHandler().isAllowed(parentDN, localOp, c)) |
| | | { |
| | | localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS); |
| | | localOp.appendErrorMessage( |
| | | ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid)); |
| | | skipPostOperation = true; |
| | | break addProcessing; |
| | | } |
| | | |
| | | 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.getMessageObject()); |
| | | |
| | | 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( |
| | | ERR_ADD_ASSERTION_FAILED.get( |
| | | String.valueOf(entryDN))); |
| | | |
| | | break addProcessing; |
| | | } |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | localOp.setResultCode(ResultCode.PROTOCOL_ERROR); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_ADD_CANNOT_PROCESS_ASSERTION_FILTER.get( |
| | | String.valueOf(entryDN), |
| | | de.getMessageObject())); |
| | | |
| | | break addProcessing; |
| | | } |
| | | } |
| | | else if (oid.equals(OID_LDAP_NOOP_OPENLDAP_ASSIGNED)) |
| | | { |
| | | noOp = true; |
| | | } |
| | | else if (oid.equals(OID_LDAP_READENTRY_POSTREAD)) |
| | | { |
| | | if (c instanceof LDAPPostReadRequestControl) |
| | | { |
| | | postReadRequest = (LDAPPostReadRequestControl) c; |
| | | } |
| | | else |
| | | { |
| | | try |
| | | { |
| | | postReadRequest = LDAPPostReadRequestControl.decodeControl(c); |
| | | requestControls.set(i, postReadRequest); |
| | | } |
| | | catch (LDAPException le) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, le); |
| | | } |
| | | |
| | | localOp.setResultCode(ResultCode.valueOf(le.getResultCode())); |
| | | localOp.appendErrorMessage(le.getMessageObject()); |
| | | |
| | | 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)) |
| | | { |
| | | localOp.appendErrorMessage( |
| | | ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get()); |
| | | 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.getMessageObject()); |
| | | |
| | | 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.getMessageObject()); |
| | | |
| | | 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)) |
| | | { |
| | | localOp.appendErrorMessage( |
| | | ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get()); |
| | | 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.getMessageObject()); |
| | | |
| | | 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.getMessageObject()); |
| | | |
| | | break addProcessing; |
| | | } |
| | | |
| | | localOp.setAuthorizationEntry(authorizationEntry); |
| | | if (authorizationEntry == null) |
| | | { |
| | | localOp.setProxiedAuthorizationDN(DN.nullDN()); |
| | | } |
| | | else |
| | | { |
| | | localOp.setProxiedAuthorizationDN(authorizationEntry.getDN()); |
| | | } |
| | | } |
| | | else if (oid.equals(OID_PASSWORD_POLICY_CONTROL)) |
| | | { |
| | | // We don't need to do anything here because it's already handled |
| | | // in LocalBackendAddOperation.handlePasswordPolicy(). |
| | | } |
| | | |
| | | // NYI -- Add support for additional controls. |
| | | else if (c.isCritical()) |
| | | { |
| | | if ((backend == null) || (! backend.supportsControl(oid))) |
| | | { |
| | | localOp.setResultCode( |
| | | ResultCode.UNAVAILABLE_CRITICAL_EXTENSION); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_ADD_UNSUPPORTED_CRITICAL_CONTROL.get( |
| | | 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); |
| | | |
| | | localOp.appendErrorMessage(ERR_ADD_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS |
| | | .get(String.valueOf(entryDN))); |
| | | |
| | | skipPostOperation = true; |
| | | break addProcessing; |
| | | } |
| | | |
| | | // Check for a request to cancel this operation. |
| | | if (localOp.getCancelRequest() != null) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | |
| | | // If the operation is not a synchronization operation, |
| | | // Invoke the pre-operation add 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); |
| | | |
| | | localOp.appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get()); |
| | | |
| | | return; |
| | | } |
| | | else if (preOpResult.sendResponseImmediately()) |
| | | { |
| | | skipPostOperation = true; |
| | | break addProcessing; |
| | | } |
| | | else if (preOpResult.skipCoreProcessing()) |
| | | { |
| | | skipPostOperation = false; |
| | | break addProcessing; |
| | | } |
| | | } |
| | | |
| | | |
| | | // Check for a request to cancel this operation. |
| | | if (localOp.getCancelRequest() != null) |
| | | { |
| | | 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(Message.raw("No backend for entry " + |
| | | entryDN.toString())); // TODO: i18n |
| | | } |
| | | 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(ERR_ADD_SERVER_READONLY.get( |
| | | String.valueOf(entryDN))); |
| | | break addProcessing; |
| | | |
| | | case INTERNAL_ONLY: |
| | | if (! (localOp.isInternalOperation() || |
| | | localOp.isSynchronizationOperation())) |
| | | { |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | localOp.appendErrorMessage(ERR_ADD_SERVER_READONLY.get( |
| | | String.valueOf(entryDN))); |
| | | break addProcessing; |
| | | } |
| | | } |
| | | |
| | | switch (backend.getWritabilityMode()) |
| | | { |
| | | case DISABLED: |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | localOp.appendErrorMessage(ERR_ADD_BACKEND_READONLY.get( |
| | | String.valueOf(entryDN))); |
| | | break addProcessing; |
| | | |
| | | case INTERNAL_ONLY: |
| | | if (! (localOp.isInternalOperation() || |
| | | localOp.isSynchronizationOperation())) |
| | | { |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | localOp.appendErrorMessage(ERR_ADD_BACKEND_READONLY.get( |
| | | String.valueOf(entryDN))); |
| | | break addProcessing; |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | try |
| | | { |
| | | if (noOp) |
| | | { |
| | | localOp.appendErrorMessage(INFO_ADD_NOOP.get()); |
| | | |
| | | localOp.setResultCode(ResultCode.NO_OPERATION); |
| | | } |
| | | 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(ERR_ADD_SYNCH_PREOP_FAILED. |
| | | get(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); |
| | | } |
| | | |
| | | |
| | | if (! noOp) |
| | | { |
| | | localOp.setResultCode(ResultCode.SUCCESS); |
| | | } |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | localOp.setResultCode(de.getResultCode()); |
| | | localOp.appendErrorMessage(de.getMessageObject()); |
| | | 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()); |
| | | |
| | | Message message = coe.getMessageObject(); |
| | | 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(ERR_ADD_SYNCH_POSTOP_FAILED. |
| | | get(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 or post-synchronization add plugins. |
| | | if (localOp.isSynchronizationOperation()) |
| | | { |
| | | if (localOp.getResultCode() == ResultCode.SUCCESS) |
| | | { |
| | | pluginConfigManager.invokePostSynchronizationAddPlugins(localOp); |
| | | } |
| | | } |
| | | else 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); |
| | | |
| | | localOp.appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get()); |
| | | |
| | | 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); |
| | | } |
| | | |
| | | Message message = ERR_ADD_ERROR_NOTIFYING_CHANGE_LISTENER.get( |
| | | getExceptionMessage(e)); |
| | | logError(message); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Performs 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); |
| | | } |
| | | |
| | | /** |
| | | * Performs a local delete operation against a local backend. |
| | | * |
| | | * @param localOp 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 a request to cancel this operation. |
| | | if (localOp.getCancelRequest() != null) |
| | | { |
| | | 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(ERR_DELETE_CANNOT_LOCK_ENTRY.get( |
| | | 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(ERR_DELETE_NO_SUCH_ENTRY.get( |
| | | 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.getMessageObject()); |
| | | 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(ERR_DELETE_SYNCH_CONFLICT_RESOLUTION_FAILED. |
| | | get(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 (!AccessControlConfigManager.getInstance(). |
| | | getAccessControlHandler().isAllowed(entryDN, localOp, c)) |
| | | { |
| | | localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid)); |
| | | skipPostOperation = true; |
| | | break deleteProcessing; |
| | | } |
| | | |
| | | 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.getMessageObject()); |
| | | |
| | | 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( |
| | | ERR_DELETE_ASSERTION_FAILED.get( |
| | | String.valueOf(entryDN))); |
| | | |
| | | break deleteProcessing; |
| | | } |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | localOp.setResultCode(ResultCode.PROTOCOL_ERROR); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_DELETE_CANNOT_PROCESS_ASSERTION_FILTER.get( |
| | | String.valueOf(entryDN), |
| | | de.getMessageObject())); |
| | | |
| | | break deleteProcessing; |
| | | } |
| | | } |
| | | else if (oid.equals(OID_LDAP_NOOP_OPENLDAP_ASSIGNED)) |
| | | { |
| | | noOp = true; |
| | | } |
| | | else if (oid.equals(OID_LDAP_READENTRY_PREREAD)) |
| | | { |
| | | if (c instanceof LDAPPreReadRequestControl) |
| | | { |
| | | preReadRequest = (LDAPPreReadRequestControl) c; |
| | | } |
| | | else |
| | | { |
| | | try |
| | | { |
| | | preReadRequest = LDAPPreReadRequestControl.decodeControl(c); |
| | | requestControls.set(i, preReadRequest); |
| | | } |
| | | catch (LDAPException le) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, le); |
| | | } |
| | | |
| | | localOp.setResultCode(ResultCode.valueOf(le.getResultCode())); |
| | | localOp.appendErrorMessage(le.getMessageObject()); |
| | | |
| | | 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)) |
| | | { |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get()); |
| | | 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.getMessageObject()); |
| | | |
| | | 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.getMessageObject()); |
| | | |
| | | 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)) |
| | | { |
| | | localOp.appendErrorMessage( |
| | | ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get()); |
| | | 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.getMessageObject()); |
| | | |
| | | 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.getMessageObject()); |
| | | |
| | | 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); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_DELETE_UNSUPPORTED_CRITICAL_CONTROL.get( |
| | | 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); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_DELETE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get( |
| | | String.valueOf(entryDN))); |
| | | |
| | | skipPostOperation = true; |
| | | break deleteProcessing; |
| | | } |
| | | |
| | | // Check for a request to cancel this operation. |
| | | if (localOp.getCancelRequest() != null) |
| | | { |
| | | 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); |
| | | |
| | | localOp.appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get()); |
| | | |
| | | localOp.setProcessingStopTime(); |
| | | return; |
| | | } |
| | | else if (preOpResult.sendResponseImmediately()) |
| | | { |
| | | skipPostOperation = true; |
| | | break deleteProcessing; |
| | | } |
| | | else if (preOpResult.skipCoreProcessing()) |
| | | { |
| | | skipPostOperation = false; |
| | | break deleteProcessing; |
| | | } |
| | | } |
| | | |
| | | |
| | | // Check for a request to cancel this operation. |
| | | if (localOp.getCancelRequest() != null) |
| | | { |
| | | 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( |
| | | ERR_DELETE_NO_SUCH_ENTRY.get(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( |
| | | ERR_DELETE_SERVER_READONLY.get(String.valueOf(entryDN))); |
| | | break deleteProcessing; |
| | | |
| | | case INTERNAL_ONLY: |
| | | if (! (localOp.isInternalOperation() || |
| | | localOp.isSynchronizationOperation())) |
| | | { |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | localOp.appendErrorMessage( |
| | | ERR_DELETE_SERVER_READONLY.get( |
| | | String.valueOf(entryDN))); |
| | | break deleteProcessing; |
| | | } |
| | | } |
| | | |
| | | switch (backend.getWritabilityMode()) |
| | | { |
| | | case DISABLED: |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | localOp.appendErrorMessage( |
| | | ERR_DELETE_BACKEND_READONLY.get(String.valueOf(entryDN))); |
| | | break deleteProcessing; |
| | | |
| | | case INTERNAL_ONLY: |
| | | if (! (localOp.isInternalOperation() || |
| | | localOp.isSynchronizationOperation())) |
| | | { |
| | | localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | localOp.appendErrorMessage( |
| | | ERR_DELETE_BACKEND_READONLY.get( |
| | | 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(ERR_DELETE_HAS_SUB_BACKEND.get( |
| | | String.valueOf(entryDN), |
| | | String.valueOf(dn))); |
| | | break deleteProcessing; |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // Actually perform the delete. |
| | | try |
| | | { |
| | | if (noOp) |
| | | { |
| | | localOp.appendErrorMessage(INFO_DELETE_NOOP.get()); |
| | | |
| | | localOp.setResultCode(ResultCode.NO_OPERATION); |
| | | } |
| | | 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(ERR_DELETE_SYNCH_PREOP_FAILED. |
| | | get(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); |
| | | } |
| | | |
| | | |
| | | if (! noOp) |
| | | { |
| | | localOp.setResultCode(ResultCode.SUCCESS); |
| | | } |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | localOp.setResultCode(de.getResultCode()); |
| | | localOp.appendErrorMessage(de.getMessageObject()); |
| | | 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()); |
| | | |
| | | Message message = coe.getMessageObject(); |
| | | 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(ERR_DELETE_SYNCH_POSTOP_FAILED. |
| | | get(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 or post-synchronization delete plugins. |
| | | if (localOp.isSynchronizationOperation()) |
| | | { |
| | | if (localOp.getResultCode() == ResultCode.SUCCESS) |
| | | { |
| | | pluginConfigManager.invokePostSynchronizationDeletePlugins(localOp); |
| | | } |
| | | } |
| | | else if (! skipPostOperation) |
| | | { |
| | | PostOperationPluginResult postOperationResult = |
| | | pluginConfigManager.invokePostOperationDeletePlugins(localOp); |
| | | if (postOperationResult.connectionTerminated()) |
| | | { |
| | | localOp.setResultCode(ResultCode.CANCELED); |
| | | |
| | | localOp.appendErrorMessage(ERR_CANCELED_BY_POSTOP_DISCONNECT.get()); |
| | | |
| | | 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); |
| | | } |
| | | |
| | | Message message = ERR_DELETE_ERROR_NOTIFYING_CHANGE_LISTENER.get( |
| | | getExceptionMessage(e)); |
| | | logError(message); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // Stop the processing timer. |
| | | localOp.setProcessingStopTime(); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Perform a compare operation against a local backend. |
| | | * |
| | | * @param operation - The operation to perform |
| | | */ |
| | | public void processCompare(CompareOperation operation) |
| | | { |
| | | LocalBackendCompareOperation localOperation = |
| | | new LocalBackendCompareOperation(operation); |
| | | processLocalCompare(localOperation); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Perform a local compare operation against a local backend. |
| | | * |
| | | * @param localOp - The operation to perform |
| | | */ |
| | | private void processLocalCompare(LocalBackendCompareOperation localOp) |
| | | { |
| | | // Get the plugin config manager that will be used for invoking plugins. |
| | | PluginConfigManager pluginConfigManager = |
| | | DirectoryServer.getPluginConfigManager(); |
| | | boolean skipPostOperation = false; |
| | | |
| | | |
| | | // Get a reference to the client connection |
| | | ClientConnection clientConnection = localOp.getClientConnection(); |
| | | |
| | | |
| | | // Check for a request to cancel this operation. |
| | | if (localOp.getCancelRequest() != null) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | |
| | | // Create a labeled block of code that we can break out of if a problem is |
| | | // detected. |
| | | compareProcessing: |
| | | { |
| | | // Process the entry DN to convert it from the raw form to the form |
| | | // required for the rest of the compare processing. |
| | | DN entryDN = localOp.getEntryDN(); |
| | | if (entryDN == null) |
| | | { |
| | | skipPostOperation = true; |
| | | break compareProcessing; |
| | | } |
| | | |
| | | |
| | | // If the target entry is in the server configuration, then make sure the |
| | | // requester has the CONFIG_READ privilege. |
| | | if (DirectoryServer.getConfigHandler().handlesEntry(entryDN) && |
| | | (! clientConnection.hasPrivilege(Privilege.CONFIG_READ, localOp))) |
| | | { |
| | | localOp.appendErrorMessage( |
| | | ERR_COMPARE_CONFIG_INSUFFICIENT_PRIVILEGES.get()); |
| | | localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS); |
| | | skipPostOperation = true; |
| | | break compareProcessing; |
| | | } |
| | | |
| | | |
| | | // Check for a request to cancel this operation. |
| | | if (localOp.getCancelRequest() != null) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | |
| | | // Grab a read lock on the entry. |
| | | Lock readLock = null; |
| | | for (int i=0; i < 3; i++) |
| | | { |
| | | readLock = LockManager.lockRead(entryDN); |
| | | if (readLock != null) |
| | | { |
| | | break; |
| | | } |
| | | } |
| | | |
| | | if (readLock == null) |
| | | { |
| | | Message message = ERR_COMPARE_CANNOT_LOCK_ENTRY.get( |
| | | String.valueOf(entryDN)); |
| | | |
| | | localOp.setResultCode(DirectoryServer.getServerErrorResultCode()); |
| | | localOp.appendErrorMessage(message); |
| | | skipPostOperation = true; |
| | | break compareProcessing; |
| | | } |
| | | |
| | | Entry entry = null; |
| | | try |
| | | { |
| | | // Get the entry. If it does not exist, then fail. |
| | | try |
| | | { |
| | | entry = DirectoryServer.getEntry(entryDN); |
| | | localOp.setEntryToCompare(entry); |
| | | |
| | | if (entry == null) |
| | | { |
| | | localOp.setResultCode(ResultCode.NO_SUCH_OBJECT); |
| | | localOp.appendErrorMessage( |
| | | ERR_COMPARE_NO_SUCH_ENTRY.get(String.valueOf(entryDN))); |
| | | |
| | | // See if one of the entry's ancestors exists. |
| | | DN parentDN = entryDN.getParentDNInSuffix(); |
| | | while (parentDN != null) |
| | | { |
| | | try |
| | | { |
| | | if (DirectoryServer.entryExists(parentDN)) |
| | | { |
| | | localOp.setMatchedDN(parentDN); |
| | | break; |
| | | } |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | break; |
| | | } |
| | | |
| | | parentDN = parentDN.getParentDNInSuffix(); |
| | | } |
| | | |
| | | break compareProcessing; |
| | | } |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | localOp.setResultCode(de.getResultCode()); |
| | | localOp.appendErrorMessage(de.getMessageObject()); |
| | | break compareProcessing; |
| | | } |
| | | |
| | | // 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 (!AccessControlConfigManager.getInstance(). |
| | | getAccessControlHandler(). |
| | | isAllowed(entryDN, localOp, c)) |
| | | { |
| | | localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid)); |
| | | skipPostOperation = true; |
| | | break compareProcessing; |
| | | } |
| | | |
| | | 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.getMessageObject()); |
| | | |
| | | break compareProcessing; |
| | | } |
| | | } |
| | | |
| | | 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( |
| | | ERR_COMPARE_ASSERTION_FAILED.get( |
| | | String.valueOf(entryDN))); |
| | | |
| | | break compareProcessing; |
| | | } |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | localOp.setResultCode(ResultCode.PROTOCOL_ERROR); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_COMPARE_CANNOT_PROCESS_ASSERTION_FILTER.get( |
| | | String.valueOf(entryDN), |
| | | de.getMessageObject())); |
| | | |
| | | break compareProcessing; |
| | | } |
| | | } |
| | | 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)) |
| | | { |
| | | localOp.appendErrorMessage( |
| | | ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get()); |
| | | localOp.setResultCode(ResultCode.AUTHORIZATION_DENIED); |
| | | break compareProcessing; |
| | | } |
| | | |
| | | |
| | | 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.getMessageObject()); |
| | | |
| | | break compareProcessing; |
| | | } |
| | | } |
| | | |
| | | |
| | | Entry authorizationEntry; |
| | | try |
| | | { |
| | | authorizationEntry = proxyControl.getAuthorizationEntry(); |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | localOp.setResultCode(de.getResultCode()); |
| | | localOp.appendErrorMessage(de.getMessageObject()); |
| | | |
| | | break compareProcessing; |
| | | } |
| | | |
| | | 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)) |
| | | { |
| | | localOp.appendErrorMessage( |
| | | ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get()); |
| | | localOp.setResultCode(ResultCode.AUTHORIZATION_DENIED); |
| | | break compareProcessing; |
| | | } |
| | | |
| | | |
| | | 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.getMessageObject()); |
| | | |
| | | break compareProcessing; |
| | | } |
| | | } |
| | | |
| | | |
| | | Entry authorizationEntry; |
| | | try |
| | | { |
| | | authorizationEntry = proxyControl.getAuthorizationEntry(); |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | localOp.setResultCode(de.getResultCode()); |
| | | localOp.appendErrorMessage(de.getMessageObject()); |
| | | |
| | | break compareProcessing; |
| | | } |
| | | |
| | | 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); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_COMPARE_UNSUPPORTED_CRITICAL_CONTROL.get( |
| | | String.valueOf(entryDN), oid)); |
| | | |
| | | break compareProcessing; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // Check to see if the client has permission to perform the |
| | | // compare. |
| | | |
| | | // 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); |
| | | |
| | | localOp.appendErrorMessage( |
| | | ERR_COMPARE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get( |
| | | String.valueOf(entryDN))); |
| | | |
| | | skipPostOperation = true; |
| | | break compareProcessing; |
| | | } |
| | | |
| | | // Check for a request to cancel this operation. |
| | | if (localOp.getCancelRequest() != null) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | |
| | | // Invoke the pre-operation compare plugins. |
| | | PreOperationPluginResult preOpResult = |
| | | pluginConfigManager.invokePreOperationComparePlugins(localOp); |
| | | if (preOpResult.connectionTerminated()) |
| | | { |
| | | // There's no point in continuing with anything. Log the request and |
| | | // result and return. |
| | | localOp.setResultCode(ResultCode.CANCELED); |
| | | |
| | | localOp.appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get()); |
| | | |
| | | return; |
| | | } |
| | | else if (preOpResult.sendResponseImmediately()) |
| | | { |
| | | skipPostOperation = true; |
| | | break compareProcessing; |
| | | } |
| | | else if (preOpResult.skipCoreProcessing()) |
| | | { |
| | | skipPostOperation = false; |
| | | break compareProcessing; |
| | | } |
| | | |
| | | |
| | | // Get the base attribute type and set of options. |
| | | String baseName; |
| | | HashSet<String> options; |
| | | String rawAttributeType = localOp.getRawAttributeType(); |
| | | int semicolonPos = rawAttributeType.indexOf(';'); |
| | | if (semicolonPos > 0) |
| | | { |
| | | baseName = toLowerCase(rawAttributeType.substring(0, semicolonPos)); |
| | | |
| | | options = new HashSet<String>(); |
| | | int nextPos = rawAttributeType.indexOf(';', semicolonPos+1); |
| | | while (nextPos > 0) |
| | | { |
| | | options.add(rawAttributeType.substring(semicolonPos+1, nextPos)); |
| | | semicolonPos = nextPos; |
| | | nextPos = rawAttributeType.indexOf(';', semicolonPos+1); |
| | | } |
| | | |
| | | options.add(rawAttributeType.substring(semicolonPos+1)); |
| | | } |
| | | else |
| | | { |
| | | baseName = toLowerCase(rawAttributeType); |
| | | options = null; |
| | | } |
| | | |
| | | |
| | | // Actually perform the compare operation. |
| | | List<Attribute> attrList = null; |
| | | if (localOp.getAttributeType() == null) |
| | | { |
| | | localOp.setAttributeType(DirectoryServer.getAttributeType(baseName)); |
| | | } |
| | | if (localOp.getAttributeType() == null) |
| | | { |
| | | attrList = entry.getAttribute(baseName, options); |
| | | localOp.setAttributeType( |
| | | DirectoryServer.getDefaultAttributeType(baseName)); |
| | | } |
| | | else |
| | | { |
| | | attrList = entry.getAttribute(localOp.getAttributeType(), options); |
| | | } |
| | | |
| | | if ((attrList == null) || attrList.isEmpty()) |
| | | { |
| | | localOp.setResultCode(ResultCode.NO_SUCH_ATTRIBUTE); |
| | | if (options == null) |
| | | { |
| | | localOp.appendErrorMessage(WARN_COMPARE_OP_NO_SUCH_ATTR.get( |
| | | String.valueOf(entryDN), baseName)); |
| | | } |
| | | else |
| | | { |
| | | localOp.appendErrorMessage( |
| | | WARN_COMPARE_OP_NO_SUCH_ATTR_WITH_OPTIONS.get( |
| | | String.valueOf(entryDN), baseName)); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | AttributeValue value = new AttributeValue( |
| | | localOp.getAttributeType(), |
| | | localOp.getAssertionValue()); |
| | | |
| | | boolean matchFound = false; |
| | | for (Attribute a : attrList) |
| | | { |
| | | if (a.hasValue(value)) |
| | | { |
| | | matchFound = true; |
| | | break; |
| | | } |
| | | } |
| | | |
| | | if (matchFound) |
| | | { |
| | | localOp.setResultCode(ResultCode.COMPARE_TRUE); |
| | | } |
| | | else |
| | | { |
| | | localOp.setResultCode(ResultCode.COMPARE_FALSE); |
| | | } |
| | | } |
| | | } |
| | | finally |
| | | { |
| | | LockManager.unlock(entryDN, readLock); |
| | | } |
| | | } |
| | | |
| | | |
| | | // Check for a request to cancel this operation. |
| | | if (localOp.getCancelRequest() != null) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | |
| | | // Invoke the post-operation compare plugins. |
| | | if (! skipPostOperation) |
| | | { |
| | | PostOperationPluginResult postOperationResult = |
| | | pluginConfigManager.invokePostOperationComparePlugins(localOp); |
| | | if (postOperationResult.connectionTerminated()) |
| | | { |
| | | localOp.setResultCode(ResultCode.CANCELED); |
| | | |
| | | localOp.appendErrorMessage(ERR_CANCELED_BY_POSTOP_DISCONNECT.get()); |
| | | |
| | | return; |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Perform a moddn operation against a local backend. |
| | | * |
| | | * @param op The operation to perform |
| | | */ |
| | | public void processModifyDN(ModifyDNOperation op) |
| | | { |
| | | LocalBackendModifyDNOperation localOp = |
| | | new LocalBackendModifyDNOperation(op); |
| | | processLocalModifyDN(localOp); |
| | | } |
| | | |
| | | /** |
| | | * Perform a local moddn operation against the local backend. |
| | | * |
| | | * @param op - The operation to perform |
| | | */ |
| | | private void processLocalModifyDN(LocalBackendModifyDNOperation op) |
| | | { |
| | | |
| | | ClientConnection clientConnection = op.getClientConnection(); |
| | | |
| | | // Get the plugin config manager that will be used for invoking plugins. |
| | | PluginConfigManager pluginConfigManager = |
| | | DirectoryServer.getPluginConfigManager(); |
| | | boolean skipPostOperation = false; |
| | | |
| | | // Check for a request to cancel this operation. |
| | | if (op.getCancelRequest() != null) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | // Create a labeled block of code that we can break out of if a problem is |
| | | // detected. |
| | | modifyDNProcessing: |
| | | { |
| | | // Process the entry DN, newRDN, and newSuperior elements from their raw |
| | | // forms as provided by the client to the forms required for the rest of |
| | | // the modify DN processing. |
| | | DN entryDN = op.getEntryDN(); |
| | | |
| | | RDN newRDN = op.getNewRDN(); |
| | | if (newRDN == null) |
| | | { |
| | | skipPostOperation = true; |
| | | break modifyDNProcessing; |
| | | } |
| | | |
| | | DN newSuperior = op.getNewSuperior(); |
| | | if ((newSuperior == null) && |
| | | (op.getRawNewSuperior() != null)) |
| | | { |
| | | skipPostOperation = true; |
| | | break modifyDNProcessing; |
| | | } |
| | | |
| | | // Construct the new DN to use for the entry. |
| | | DN parentDN; |
| | | if (newSuperior == null) |
| | | { |
| | | parentDN = entryDN.getParentDNInSuffix(); |
| | | } |
| | | else |
| | | { |
| | | parentDN = newSuperior; |
| | | } |
| | | |
| | | if ((parentDN == null) || parentDN.isNullDN()) |
| | | { |
| | | op.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | op.appendErrorMessage(ERR_MODDN_NO_PARENT.get(String.valueOf(entryDN))); |
| | | break modifyDNProcessing; |
| | | } |
| | | |
| | | DN newDN = parentDN.concat(newRDN); |
| | | |
| | | // Get the backend for the current entry, and the backend for the new |
| | | // entry. If either is null, or if they are different, then fail. |
| | | Backend currentBackend = backend; |
| | | if (currentBackend == null) |
| | | { |
| | | op.setResultCode(ResultCode.NO_SUCH_OBJECT); |
| | | op.appendErrorMessage(ERR_MODDN_NO_BACKEND_FOR_CURRENT_ENTRY.get( |
| | | String.valueOf(entryDN))); |
| | | break modifyDNProcessing; |
| | | } |
| | | |
| | | Backend newBackend = DirectoryServer.getBackend(newDN); |
| | | if (newBackend == null) |
| | | { |
| | | op.setResultCode(ResultCode.NO_SUCH_OBJECT); |
| | | op.appendErrorMessage(ERR_MODDN_NO_BACKEND_FOR_NEW_ENTRY.get( |
| | | String.valueOf(entryDN), |
| | | String.valueOf(newDN))); |
| | | break modifyDNProcessing; |
| | | } |
| | | else if (! currentBackend.equals(newBackend)) |
| | | { |
| | | op.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | op.appendErrorMessage(ERR_MODDN_DIFFERENT_BACKENDS.get( |
| | | String.valueOf(entryDN), |
| | | String.valueOf(newDN))); |
| | | break modifyDNProcessing; |
| | | } |
| | | |
| | | |
| | | // Check for a request to cancel this operation. |
| | | if (op.getCancelRequest() != null) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | |
| | | // Acquire write locks for the current and new DN. |
| | | Lock currentLock = null; |
| | | for (int i=0; i < 3; i++) |
| | | { |
| | | currentLock = LockManager.lockWrite(entryDN); |
| | | if (currentLock != null) |
| | | { |
| | | break; |
| | | } |
| | | } |
| | | |
| | | if (currentLock == null) |
| | | { |
| | | op.setResultCode(DirectoryServer.getServerErrorResultCode()); |
| | | op.appendErrorMessage(ERR_MODDN_CANNOT_LOCK_CURRENT_DN.get( |
| | | String.valueOf(entryDN))); |
| | | |
| | | skipPostOperation = true; |
| | | break modifyDNProcessing; |
| | | } |
| | | |
| | | Lock newLock = null; |
| | | try |
| | | { |
| | | for (int i=0; i < 3; i++) |
| | | { |
| | | newLock = LockManager.lockWrite(newDN); |
| | | if (newLock != null) |
| | | { |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | |
| | | LockManager.unlock(entryDN, currentLock); |
| | | |
| | | if (newLock != null) |
| | | { |
| | | LockManager.unlock(newDN, newLock); |
| | | } |
| | | |
| | | op.setResultCode(DirectoryServer.getServerErrorResultCode()); |
| | | op.appendErrorMessage(ERR_MODDN_EXCEPTION_LOCKING_NEW_DN.get( |
| | | String.valueOf(entryDN), |
| | | String.valueOf(newDN), |
| | | getExceptionMessage(e))); |
| | | |
| | | skipPostOperation = true; |
| | | break modifyDNProcessing; |
| | | } |
| | | |
| | | if (newLock == null) |
| | | { |
| | | LockManager.unlock(entryDN, currentLock); |
| | | |
| | | op.setResultCode(DirectoryServer.getServerErrorResultCode()); |
| | | op.appendErrorMessage(ERR_MODDN_CANNOT_LOCK_NEW_DN.get( |
| | | String.valueOf(entryDN), |
| | | String.valueOf(newDN))); |
| | | |
| | | skipPostOperation = true; |
| | | break modifyDNProcessing; |
| | | } |
| | | |
| | | Entry currentEntry = null; |
| | | try |
| | | { |
| | | // Check for a request to cancel this operation. |
| | | if (op.getCancelRequest() != null) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | |
| | | // Get the current entry from the appropriate backend. If it doesn't |
| | | // exist, then fail. |
| | | try |
| | | { |
| | | currentEntry = currentBackend.getEntry(entryDN); |
| | | op.setOriginalEntry(currentEntry); |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | op.setResultCode(de.getResultCode()); |
| | | op.appendErrorMessage(de.getMessageObject()); |
| | | op.setMatchedDN(de.getMatchedDN()); |
| | | op.setReferralURLs(de.getReferralURLs()); |
| | | |
| | | break modifyDNProcessing; |
| | | } |
| | | |
| | | if (op.getOriginalEntry() == null) |
| | | { |
| | | // See if one of the entry's ancestors exists. |
| | | parentDN = entryDN.getParentDNInSuffix(); |
| | | while (parentDN != null) |
| | | { |
| | | try |
| | | { |
| | | if (DirectoryServer.entryExists(parentDN)) |
| | | { |
| | | op.setMatchedDN(parentDN); |
| | | break; |
| | | } |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | break; |
| | | } |
| | | |
| | | parentDN = parentDN.getParentDNInSuffix(); |
| | | } |
| | | |
| | | op.setResultCode(ResultCode.NO_SUCH_OBJECT); |
| | | op.appendErrorMessage(ERR_MODDN_NO_CURRENT_ENTRY.get( |
| | | String.valueOf(entryDN))); |
| | | |
| | | break modifyDNProcessing; |
| | | } |
| | | |
| | | |
| | | // Invoke any conflict resolution processing that might be needed by the |
| | | // synchronization provider. |
| | | for (SynchronizationProvider provider : |
| | | DirectoryServer.getSynchronizationProviders()) |
| | | { |
| | | try |
| | | { |
| | | SynchronizationProviderResult result = |
| | | provider.handleConflictResolution(op); |
| | | if (! result.continueOperationProcessing()) |
| | | { |
| | | break modifyDNProcessing; |
| | | } |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | logError(ERR_MODDN_SYNCH_CONFLICT_RESOLUTION_FAILED. |
| | | get(op.getConnectionID(), op.getOperationID(), |
| | | getExceptionMessage(de))); |
| | | |
| | | op.setResponseData(de); |
| | | break modifyDNProcessing; |
| | | } |
| | | } |
| | | |
| | | |
| | | // 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 = op.getRequestControls(); |
| | | if ((requestControls != null) && (! requestControls.isEmpty())) |
| | | { |
| | | for (int i=0; i < requestControls.size(); i++) |
| | | { |
| | | Control c = requestControls.get(i); |
| | | String oid = c.getOID(); |
| | | |
| | | if (!AccessControlConfigManager.getInstance(). |
| | | getAccessControlHandler().isAllowed(entryDN, op, c)) |
| | | { |
| | | op.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS); |
| | | |
| | | op.appendErrorMessage( |
| | | ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid)); |
| | | skipPostOperation = true; |
| | | break modifyDNProcessing; |
| | | } |
| | | |
| | | 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); |
| | | } |
| | | |
| | | op.setResultCode(ResultCode.valueOf(le.getResultCode())); |
| | | op.appendErrorMessage(le.getMessageObject()); |
| | | |
| | | break modifyDNProcessing; |
| | | } |
| | | } |
| | | |
| | | try |
| | | { |
| | | // FIXME -- We need to determine whether the current user has |
| | | // permission to make this determination. |
| | | SearchFilter filter = assertControl.getSearchFilter(); |
| | | if (! filter.matchesEntry(currentEntry)) |
| | | { |
| | | op.setResultCode(ResultCode.ASSERTION_FAILED); |
| | | |
| | | op.appendErrorMessage(ERR_MODDN_ASSERTION_FAILED.get( |
| | | String.valueOf(entryDN))); |
| | | |
| | | break modifyDNProcessing; |
| | | } |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | op.setResultCode(ResultCode.PROTOCOL_ERROR); |
| | | |
| | | op.appendErrorMessage( |
| | | ERR_MODDN_CANNOT_PROCESS_ASSERTION_FILTER.get( |
| | | String.valueOf(entryDN), |
| | | de.getMessageObject())); |
| | | |
| | | break modifyDNProcessing; |
| | | } |
| | | } |
| | | else if (oid.equals(OID_LDAP_NOOP_OPENLDAP_ASSIGNED)) |
| | | { |
| | | noOp = true; |
| | | } |
| | | else if (oid.equals(OID_LDAP_READENTRY_PREREAD)) |
| | | { |
| | | if (c instanceof LDAPPreReadRequestControl) |
| | | { |
| | | preReadRequest = (LDAPPreReadRequestControl) c; |
| | | } |
| | | else |
| | | { |
| | | try |
| | | { |
| | | preReadRequest = LDAPPreReadRequestControl.decodeControl(c); |
| | | requestControls.set(i, preReadRequest); |
| | | } |
| | | catch (LDAPException le) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, le); |
| | | } |
| | | |
| | | op.setResultCode(ResultCode.valueOf(le.getResultCode())); |
| | | op.appendErrorMessage(le.getMessageObject()); |
| | | |
| | | break modifyDNProcessing; |
| | | } |
| | | } |
| | | } |
| | | else if (oid.equals(OID_LDAP_READENTRY_POSTREAD)) |
| | | { |
| | | if (c instanceof LDAPPostReadRequestControl) |
| | | { |
| | | postReadRequest = (LDAPPostReadRequestControl) c; |
| | | } |
| | | else |
| | | { |
| | | try |
| | | { |
| | | postReadRequest = LDAPPostReadRequestControl.decodeControl(c); |
| | | requestControls.set(i, postReadRequest); |
| | | } |
| | | catch (LDAPException le) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, le); |
| | | } |
| | | |
| | | op.setResultCode(ResultCode.valueOf(le.getResultCode())); |
| | | op.appendErrorMessage(le.getMessageObject()); |
| | | |
| | | break modifyDNProcessing; |
| | | } |
| | | } |
| | | } |
| | | 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, op)) |
| | | { |
| | | |
| | | op.appendErrorMessage( |
| | | ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get()); |
| | | op.setResultCode(ResultCode.AUTHORIZATION_DENIED); |
| | | break modifyDNProcessing; |
| | | } |
| | | |
| | | |
| | | 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); |
| | | } |
| | | |
| | | op.setResultCode(ResultCode.valueOf(le.getResultCode())); |
| | | op.appendErrorMessage(le.getMessageObject()); |
| | | |
| | | break modifyDNProcessing; |
| | | } |
| | | } |
| | | |
| | | |
| | | Entry authorizationEntry; |
| | | try |
| | | { |
| | | authorizationEntry = proxyControl.getAuthorizationEntry(); |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | op.setResultCode(de.getResultCode()); |
| | | op.appendErrorMessage(de.getMessageObject()); |
| | | |
| | | break modifyDNProcessing; |
| | | } |
| | | |
| | | op.setAuthorizationEntry(authorizationEntry); |
| | | if (authorizationEntry == null) |
| | | { |
| | | op.setProxiedAuthorizationDN(DN.nullDN()); |
| | | } |
| | | else |
| | | { |
| | | op.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, op)) |
| | | { |
| | | |
| | | op.appendErrorMessage( |
| | | ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get()); |
| | | op.setResultCode(ResultCode.AUTHORIZATION_DENIED); |
| | | break modifyDNProcessing; |
| | | } |
| | | |
| | | |
| | | 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); |
| | | } |
| | | |
| | | op.setResultCode(ResultCode.valueOf(le.getResultCode())); |
| | | op.appendErrorMessage(le.getMessageObject()); |
| | | |
| | | break modifyDNProcessing; |
| | | } |
| | | } |
| | | |
| | | |
| | | Entry authorizationEntry; |
| | | try |
| | | { |
| | | authorizationEntry = proxyControl.getAuthorizationEntry(); |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | op.setResultCode(de.getResultCode()); |
| | | op.appendErrorMessage(de.getMessageObject()); |
| | | |
| | | break modifyDNProcessing; |
| | | } |
| | | |
| | | op.setAuthorizationEntry(authorizationEntry); |
| | | if (authorizationEntry == null) |
| | | { |
| | | op.setProxiedAuthorizationDN(DN.nullDN()); |
| | | } |
| | | else |
| | | { |
| | | op.setProxiedAuthorizationDN(authorizationEntry.getDN()); |
| | | } |
| | | } |
| | | |
| | | // NYI -- Add support for additional controls. |
| | | else if (c.isCritical()) |
| | | { |
| | | Backend backend = DirectoryServer.getBackend(entryDN); |
| | | if ((backend == null) || (! backend.supportsControl(oid))) |
| | | { |
| | | op.setResultCode(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION); |
| | | |
| | | |
| | | op.appendErrorMessage( |
| | | ERR_MODDN_UNSUPPORTED_CRITICAL_CONTROL.get( |
| | | String.valueOf(entryDN), |
| | | oid)); |
| | | |
| | | break modifyDNProcessing; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // Check to see if the client has permission to perform the |
| | | // modify DN. |
| | | |
| | | // 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 or new superior |
| | | // already exists may have already exposed sensitive information |
| | | // to the client. |
| | | if (AccessControlConfigManager.getInstance() |
| | | .getAccessControlHandler().isAllowed(op) == false) { |
| | | op.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS); |
| | | |
| | | op.appendErrorMessage(ERR_MODDN_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get( |
| | | String.valueOf(entryDN))); |
| | | |
| | | skipPostOperation = true; |
| | | break modifyDNProcessing; |
| | | } |
| | | |
| | | // Duplicate the entry and set its new DN. Also, create an empty list |
| | | // to hold the attribute-level modifications. |
| | | Entry newEntry = currentEntry.duplicate(false); |
| | | newEntry.setDN(newDN); |
| | | op.setUpdatedEntry(newEntry); |
| | | |
| | | // init the modifications |
| | | op.addModification(null); |
| | | List<Modification> modifications = op.getModifications(); |
| | | |
| | | |
| | | // If we should delete the old RDN values from the entry, then do so. |
| | | if (op.deleteOldRDN()) |
| | | { |
| | | RDN currentRDN = entryDN.getRDN(); |
| | | int numValues = currentRDN.getNumValues(); |
| | | for (int i=0; i < numValues; i++) |
| | | { |
| | | LinkedHashSet<AttributeValue> valueSet = |
| | | new LinkedHashSet<AttributeValue>(1); |
| | | valueSet.add(currentRDN.getAttributeValue(i)); |
| | | |
| | | Attribute a = new Attribute(currentRDN.getAttributeType(i), |
| | | currentRDN.getAttributeName(i), |
| | | valueSet); |
| | | |
| | | // If the associated attribute type is marked NO-USER-MODIFICATION, |
| | | // then refuse the update. |
| | | if (a.getAttributeType().isNoUserModification()) |
| | | { |
| | | if (! (op.isInternalOperation() || |
| | | op.isSynchronizationOperation())) |
| | | { |
| | | op.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | |
| | | op.appendErrorMessage(ERR_MODDN_OLD_RDN_ATTR_IS_NO_USER_MOD.get( |
| | | String.valueOf(entryDN), a.getName())); |
| | | break modifyDNProcessing; |
| | | } |
| | | } |
| | | |
| | | LinkedList<AttributeValue> missingValues = |
| | | new LinkedList<AttributeValue>(); |
| | | newEntry.removeAttribute(a, missingValues); |
| | | |
| | | if (missingValues.isEmpty()) |
| | | { |
| | | modifications.add(new Modification(ModificationType.DELETE, a)); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // Add the new RDN values to the entry. |
| | | int newRDNValues = newRDN.getNumValues(); |
| | | for (int i=0; i < newRDNValues; i++) |
| | | { |
| | | LinkedHashSet<AttributeValue> valueSet = |
| | | new LinkedHashSet<AttributeValue>(1); |
| | | valueSet.add(newRDN.getAttributeValue(i)); |
| | | |
| | | Attribute a = new Attribute(newRDN.getAttributeType(i), |
| | | newRDN.getAttributeName(i), |
| | | valueSet); |
| | | |
| | | LinkedList<AttributeValue> duplicateValues = |
| | | new LinkedList<AttributeValue>(); |
| | | newEntry.addAttribute(a, duplicateValues); |
| | | |
| | | if (duplicateValues.isEmpty()) |
| | | { |
| | | // If the associated attribute type is marked NO-USER-MODIFICATION, |
| | | // then refuse the update. |
| | | if (a.getAttributeType().isNoUserModification()) |
| | | { |
| | | if (! (op.isInternalOperation() || |
| | | op.isSynchronizationOperation())) |
| | | { |
| | | op.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | |
| | | op.appendErrorMessage(ERR_MODDN_NEW_RDN_ATTR_IS_NO_USER_MOD.get( |
| | | String.valueOf(entryDN), a.getName())); |
| | | break modifyDNProcessing; |
| | | } |
| | | } |
| | | else |
| | | { |
| | | modifications.add(new Modification(ModificationType.ADD, a)); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // If the server is configured to check the schema and the |
| | | // operation is not a synchronization operation, |
| | | // make sure that the resulting entry is valid as per the server schema. |
| | | if ((DirectoryServer.checkSchema()) && |
| | | (!op.isSynchronizationOperation()) ) |
| | | { |
| | | MessageBuilder invalidReason = new MessageBuilder(); |
| | | if (! newEntry.conformsToSchema(null, false, true, true, |
| | | invalidReason)) |
| | | { |
| | | op.setResultCode(ResultCode.OBJECTCLASS_VIOLATION); |
| | | op.appendErrorMessage(ERR_MODDN_VIOLATES_SCHEMA.get( |
| | | String.valueOf(entryDN), |
| | | String.valueOf(invalidReason))); |
| | | break modifyDNProcessing; |
| | | } |
| | | |
| | | for (int i=0; i < newRDNValues; i++) |
| | | { |
| | | AttributeType at = newRDN.getAttributeType(i); |
| | | if (at.isObsolete()) |
| | | { |
| | | op.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | op.appendErrorMessage(ERR_MODDN_NEWRDN_ATTR_IS_OBSOLETE.get( |
| | | String.valueOf(entryDN), |
| | | at.getNameOrOID())); |
| | | break modifyDNProcessing; |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // Check for a request to cancel this operation. |
| | | if (op.getCancelRequest() != null) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | |
| | | // Get a count of the current number of modifications. The |
| | | // pre-operation plugins may alter this list, and we need to be able to |
| | | // identify which changes were made after they're done. |
| | | int modCount = op.getModifications().size(); |
| | | |
| | | |
| | | // If the operation is not a synchronization operation, |
| | | // Invoke the pre-operation modify DN plugins. |
| | | if (!op.isSynchronizationOperation()) |
| | | { |
| | | PreOperationPluginResult preOpResult = |
| | | pluginConfigManager.invokePreOperationModifyDNPlugins(op); |
| | | if (preOpResult.connectionTerminated()) |
| | | { |
| | | // There's no point in continuing with anything. Log the request |
| | | // and result and return. |
| | | op.setResultCode(ResultCode.CANCELED); |
| | | |
| | | |
| | | op.appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get()); |
| | | return; |
| | | } |
| | | else if (preOpResult.sendResponseImmediately()) |
| | | { |
| | | skipPostOperation = true; |
| | | break modifyDNProcessing; |
| | | } |
| | | else if (preOpResult.skipCoreProcessing()) |
| | | { |
| | | skipPostOperation = false; |
| | | break modifyDNProcessing; |
| | | } |
| | | } |
| | | |
| | | // Check to see if any of the pre-operation plugins made any changes to |
| | | // the entry. If so, then apply them. |
| | | if (modifications.size() > modCount) |
| | | { |
| | | for (int i=modCount; i < modifications.size(); i++) |
| | | { |
| | | Modification m = modifications.get(i); |
| | | Attribute a = m.getAttribute(); |
| | | |
| | | switch (m.getModificationType()) |
| | | { |
| | | case ADD: |
| | | LinkedList<AttributeValue> duplicateValues = |
| | | new LinkedList<AttributeValue>(); |
| | | newEntry.addAttribute(a, duplicateValues); |
| | | break; |
| | | case DELETE: |
| | | LinkedList<AttributeValue> missingValues = |
| | | new LinkedList<AttributeValue>(); |
| | | newEntry.removeAttribute(a, missingValues); |
| | | break; |
| | | case REPLACE: |
| | | duplicateValues = new LinkedList<AttributeValue>(); |
| | | newEntry.removeAttribute(a.getAttributeType(), a.getOptions()); |
| | | newEntry.addAttribute(a, duplicateValues); |
| | | break; |
| | | case INCREMENT: |
| | | List<Attribute> attrList = |
| | | newEntry.getAttribute(a.getAttributeType(), |
| | | a.getOptions()); |
| | | if ((attrList == null) || attrList.isEmpty()) |
| | | { |
| | | op.setResultCode(ResultCode.NO_SUCH_ATTRIBUTE); |
| | | |
| | | op.appendErrorMessage(ERR_MODDN_PREOP_INCREMENT_NO_ATTR.get( |
| | | String.valueOf(entryDN), a.getName())); |
| | | |
| | | break modifyDNProcessing; |
| | | } |
| | | else if (attrList.size() > 1) |
| | | { |
| | | op.setResultCode(ResultCode.CONSTRAINT_VIOLATION); |
| | | |
| | | op.appendErrorMessage( |
| | | ERR_MODDN_PREOP_INCREMENT_MULTIPLE_VALUES.get( |
| | | String.valueOf(entryDN), |
| | | a.getName())); |
| | | |
| | | break modifyDNProcessing; |
| | | } |
| | | |
| | | LinkedHashSet<AttributeValue> values = |
| | | attrList.get(0).getValues(); |
| | | if ((values == null) || values.isEmpty()) |
| | | { |
| | | op.setResultCode(ResultCode.NO_SUCH_ATTRIBUTE); |
| | | |
| | | op.appendErrorMessage(ERR_MODDN_PREOP_INCREMENT_NO_ATTR.get( |
| | | String.valueOf(entryDN), |
| | | a.getName())); |
| | | |
| | | break modifyDNProcessing; |
| | | } |
| | | else if (values.size() > 1) |
| | | { |
| | | op.setResultCode(ResultCode.CONSTRAINT_VIOLATION); |
| | | |
| | | op.appendErrorMessage( |
| | | ERR_MODDN_PREOP_INCREMENT_MULTIPLE_VALUES.get( |
| | | String.valueOf(entryDN), |
| | | a.getName())); |
| | | |
| | | break modifyDNProcessing; |
| | | } |
| | | |
| | | long currentLongValue; |
| | | try |
| | | { |
| | | AttributeValue v = values.iterator().next(); |
| | | currentLongValue = Long.parseLong(v.getStringValue()); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | |
| | | op.setResultCode(ResultCode.CONSTRAINT_VIOLATION); |
| | | |
| | | op.appendErrorMessage( |
| | | ERR_MODDN_PREOP_INCREMENT_VALUE_NOT_INTEGER.get( |
| | | String.valueOf(entryDN), |
| | | a.getName())); |
| | | |
| | | break modifyDNProcessing; |
| | | } |
| | | |
| | | LinkedHashSet<AttributeValue> newValues = a.getValues(); |
| | | if ((newValues == null) || newValues.isEmpty()) |
| | | { |
| | | op.setResultCode(ResultCode.CONSTRAINT_VIOLATION); |
| | | |
| | | op.appendErrorMessage( |
| | | ERR_MODDN_PREOP_INCREMENT_NO_AMOUNT.get( |
| | | String.valueOf(entryDN), |
| | | a.getName())); |
| | | |
| | | break modifyDNProcessing; |
| | | } |
| | | else if (newValues.size() > 1) |
| | | { |
| | | op.setResultCode(ResultCode.CONSTRAINT_VIOLATION); |
| | | |
| | | op.appendErrorMessage( |
| | | ERR_MODDN_PREOP_INCREMENT_MULTIPLE_AMOUNTS.get( |
| | | String.valueOf(entryDN), |
| | | a.getName())); |
| | | |
| | | break modifyDNProcessing; |
| | | } |
| | | |
| | | long incrementAmount; |
| | | try |
| | | { |
| | | AttributeValue v = values.iterator().next(); |
| | | incrementAmount = Long.parseLong(v.getStringValue()); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | |
| | | op.setResultCode(ResultCode.CONSTRAINT_VIOLATION); |
| | | |
| | | op.appendErrorMessage( |
| | | ERR_MODDN_PREOP_INCREMENT_AMOUNT_NOT_INTEGER.get( |
| | | String.valueOf(entryDN), |
| | | a.getName())); |
| | | |
| | | break modifyDNProcessing; |
| | | } |
| | | |
| | | long newLongValue = currentLongValue + incrementAmount; |
| | | ByteString newValueOS = |
| | | new ASN1OctetString(String.valueOf(newLongValue)); |
| | | |
| | | newValues = new LinkedHashSet<AttributeValue>(1); |
| | | newValues.add(new AttributeValue(a.getAttributeType(), |
| | | newValueOS)); |
| | | |
| | | List<Attribute> newAttrList = new ArrayList<Attribute>(1); |
| | | newAttrList.add(new Attribute(a.getAttributeType(), |
| | | a.getName(), newValues)); |
| | | newEntry.putAttribute(a.getAttributeType(), newAttrList); |
| | | |
| | | break; |
| | | } |
| | | } |
| | | |
| | | |
| | | // Make sure that the updated entry still conforms to the server |
| | | // schema. |
| | | if (DirectoryServer.checkSchema()) |
| | | { |
| | | MessageBuilder invalidReason = new MessageBuilder(); |
| | | if (! newEntry.conformsToSchema(null, false, true, true, |
| | | invalidReason)) |
| | | { |
| | | op.setResultCode(ResultCode.OBJECTCLASS_VIOLATION); |
| | | |
| | | op.appendErrorMessage(ERR_MODDN_PREOP_VIOLATES_SCHEMA.get( |
| | | String.valueOf(entryDN), |
| | | String.valueOf(invalidReason))); |
| | | break modifyDNProcessing; |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // Check for a request to cancel this operation. |
| | | if (op.getCancelRequest() != null) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | |
| | | // Actually perform the modify DN operation. |
| | | // This should include taking |
| | | // care of any synchronization that might be needed. |
| | | try |
| | | { |
| | | // If it is not a private backend, then check to see if the server or |
| | | // backend is operating in read-only mode. |
| | | if (! currentBackend.isPrivateBackend()) |
| | | { |
| | | switch (DirectoryServer.getWritabilityMode()) |
| | | { |
| | | case DISABLED: |
| | | op.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | op.appendErrorMessage(ERR_MODDN_SERVER_READONLY.get( |
| | | String.valueOf(entryDN))); |
| | | break modifyDNProcessing; |
| | | |
| | | case INTERNAL_ONLY: |
| | | if (! (op.isInternalOperation() || |
| | | op.isSynchronizationOperation())) |
| | | { |
| | | op.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | op.appendErrorMessage(ERR_MODDN_SERVER_READONLY.get( |
| | | String.valueOf(entryDN))); |
| | | break modifyDNProcessing; |
| | | } |
| | | } |
| | | |
| | | switch (currentBackend.getWritabilityMode()) |
| | | { |
| | | case DISABLED: |
| | | op.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | op.appendErrorMessage(ERR_MODDN_BACKEND_READONLY.get( |
| | | String.valueOf(entryDN))); |
| | | break modifyDNProcessing; |
| | | |
| | | case INTERNAL_ONLY: |
| | | if (! (op.isInternalOperation() || |
| | | op.isSynchronizationOperation())) |
| | | { |
| | | op.setResultCode(ResultCode.UNWILLING_TO_PERFORM); |
| | | op.appendErrorMessage(ERR_MODDN_BACKEND_READONLY.get( |
| | | String.valueOf(entryDN))); |
| | | break modifyDNProcessing; |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | if (noOp) |
| | | { |
| | | op.appendErrorMessage(INFO_MODDN_NOOP.get()); |
| | | |
| | | op.setResultCode(ResultCode.NO_OPERATION); |
| | | } |
| | | else |
| | | { |
| | | for (SynchronizationProvider provider : |
| | | DirectoryServer.getSynchronizationProviders()) |
| | | { |
| | | try |
| | | { |
| | | SynchronizationProviderResult result = |
| | | provider.doPreOperation(op); |
| | | if (! result.continueOperationProcessing()) |
| | | { |
| | | break modifyDNProcessing; |
| | | } |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | logError(ERR_MODDN_SYNCH_PREOP_FAILED. |
| | | get(op.getConnectionID(), op.getOperationID(), |
| | | getExceptionMessage(de))); |
| | | |
| | | op.setResponseData(de); |
| | | break modifyDNProcessing; |
| | | } |
| | | } |
| | | |
| | | currentBackend.renameEntry(entryDN, newEntry, op); |
| | | } |
| | | |
| | | 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); |
| | | |
| | | op.addResponseControl(responseControl); |
| | | } |
| | | |
| | | if (postReadRequest != null) |
| | | { |
| | | Entry entry = newEntry.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); |
| | | |
| | | op.addResponseControl(responseControl); |
| | | } |
| | | |
| | | |
| | | if (! noOp) |
| | | { |
| | | op.setResultCode(ResultCode.SUCCESS); |
| | | } |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | op.setResultCode(de.getResultCode()); |
| | | op.appendErrorMessage(de.getMessageObject()); |
| | | op.setMatchedDN(de.getMatchedDN()); |
| | | op.setReferralURLs(de.getReferralURLs()); |
| | | |
| | | break modifyDNProcessing; |
| | | } |
| | | catch (CancelledOperationException coe) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, coe); |
| | | } |
| | | |
| | | CancelResult cancelResult = coe.getCancelResult(); |
| | | |
| | | op.setCancelResult(cancelResult); |
| | | op.setResultCode(cancelResult.getResultCode()); |
| | | |
| | | Message message = coe.getMessageObject(); |
| | | if ((message != null) && (message.length() > 0)) |
| | | { |
| | | op.appendErrorMessage(message); |
| | | } |
| | | |
| | | break modifyDNProcessing; |
| | | } |
| | | } |
| | | finally |
| | | { |
| | | LockManager.unlock(entryDN, currentLock); |
| | | LockManager.unlock(newDN, newLock); |
| | | |
| | | for (SynchronizationProvider provider : |
| | | DirectoryServer.getSynchronizationProviders()) |
| | | { |
| | | try |
| | | { |
| | | provider.doPostOperation(op); |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | logError(ERR_MODDN_SYNCH_POSTOP_FAILED. |
| | | get(op.getConnectionID(), op.getOperationID(), |
| | | getExceptionMessage(de))); |
| | | |
| | | op.setResponseData(de); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // Indicate that it is now too late to attempt to cancel the operation. |
| | | op.setCancelResult(CancelResult.TOO_LATE); |
| | | |
| | | |
| | | // Invoke the post-operation or post-synchronization modify DN plugins. |
| | | if (op.isSynchronizationOperation()) |
| | | { |
| | | if (op.getResultCode() == ResultCode.SUCCESS) |
| | | { |
| | | pluginConfigManager.invokePostSynchronizationModifyDNPlugins(op); |
| | | } |
| | | } |
| | | else if (! skipPostOperation) |
| | | { |
| | | PostOperationPluginResult postOperationResult = |
| | | pluginConfigManager.invokePostOperationModifyDNPlugins(op); |
| | | if (postOperationResult.connectionTerminated()) |
| | | { |
| | | op.setResultCode(ResultCode.CANCELED); |
| | | |
| | | |
| | | op.appendErrorMessage(ERR_CANCELED_BY_POSTOP_DISCONNECT.get()); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | |
| | | // Notify any change notification listeners that might be registered with |
| | | // the server. |
| | | if (op.getResultCode() == ResultCode.SUCCESS) |
| | | { |
| | | for (ChangeNotificationListener changeListener : |
| | | DirectoryServer.getChangeNotificationListeners()) |
| | | { |
| | | try |
| | | { |
| | | changeListener.handleModifyDNOperation(op, |
| | | op.getOriginalEntry(), |
| | | op.getUpdatedEntry()); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | |
| | | Message message = ERR_MODDN_ERROR_NOTIFYING_CHANGE_LISTENER.get( |
| | | getExceptionMessage(e)); |
| | | logError(message); |
| | | } |
| | | } |
| | | } |
| | | |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Attaches the current local operation to the global operation so that |
| | |
| | | * operation |
| | | */ |
| | | @SuppressWarnings("unchecked") |
| | | public static final <O extends Operation, L> |
| | | void attachLocalOperation ( |
| | | O globalOperation, |
| | | L currentLocalOperation |
| | | ) |
| | | public static final <O extends Operation,L> void |
| | | attachLocalOperation (O globalOperation, L currentLocalOperation) |
| | | { |
| | | List<?> existingAttachment = |
| | | (List<?>) globalOperation.getAttachment(Operation.LOCALBACKENDOPERATIONS); |
| | |
| | | newAttachment.addAll ((List<L>) existingAttachment); |
| | | } |
| | | newAttachment.add (currentLocalOperation); |
| | | globalOperation.setAttachment( |
| | | Operation.LOCALBACKENDOPERATIONS, newAttachment); |
| | | globalOperation.setAttachment(Operation.LOCALBACKENDOPERATIONS, |
| | | newAttachment); |
| | | } |
| | | |
| | | } |
| | | |