LocalBackendWorkflowElement.java revision 776a73e6a9d48721747ed06cf70746b661d3b03a
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at legal-notices/CDDLv1_0.txt.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2008-2010 Sun Microsystems, Inc.
* Portions Copyright 2011-2015 ForgeRock AS
*/
/**
* This class defines a local backend workflow element; e-g an entity that
* handle the processing of an operation against a local backend.
*/
public class LocalBackendWorkflowElement
{
/**
* This class implements the workflow result code. The workflow result code
* contains an LDAP result code along with an LDAP error message.
*/
private static class SearchResultCode
{
/** The global result code. */
/** The global error message. */
private LocalizableMessageBuilder errorMessage = new LocalizableMessageBuilder(LocalizableMessage.EMPTY);
/**
* Creates a new instance of a workflow result code and initializes it with
* a result code and an error message.
*
* @param resultCode
* the initial value for the result code
* @param errorMessage
* the initial value for the error message
*/
{
this.resultCode = resultCode;
this.errorMessage = errorMessage;
}
/**
* Elaborates a global result code. A workflow may execute an operation on
* several subordinate workflows. In such case, the parent workflow has to
* take into account all the subordinate result codes to elaborate a global
* result code. Sometimes, a referral result code has to be turned into a
* reference entry. When such case is occurring the
* elaborateGlobalResultCode method will return true. The global result code
* is elaborated as follows:
*
* <PRE>
* -----------+------------+------------+-------------------------------
* new | current | resulting |
* resultCode | resultCode | resultCode | action
* -----------+------------+------------+-------------------------------
* SUCCESS NO_SUCH_OBJ SUCCESS -
* REFERRAL SUCCESS send reference entry to client
* other [unchanged] -
* ---------------------------------------------------------------------
* NO_SUCH_OBJ SUCCESS [unchanged] -
* REFERRAL [unchanged] -
* other [unchanged] -
* ---------------------------------------------------------------------
* REFERRAL SUCCESS [unchanged] send reference entry to client
* REFERRAL SUCCESS send reference entry to client
* NO_SUCH_OBJ REFERRAL -
* other [unchanged] send reference entry to client
* ---------------------------------------------------------------------
* others SUCCESS other -
* REFERRAL other send reference entry to client
* NO_SUCH_OBJ other -
* other2 [unchanged] -
* ---------------------------------------------------------------------
* </PRE>
*
* @param newResultCode
* the new result code to take into account
* @param newErrorMessage
* the new error message associated to the new error code
* @return <code>true</code> if a referral result code must be turned into a
* reference entry
*/
private boolean elaborateGlobalResultCode(ResultCode newResultCode, LocalizableMessageBuilder newErrorMessage)
{
// if global result code has not been set yet then just take the new
// result code as is
{
return false;
}
// Elaborate the new result code (see table in the description header).
switch (newResultCode.asEnum())
{
case SUCCESS:
switch (resultCode.asEnum())
{
case NO_SUCH_OBJECT:
return false;
case REFERRAL:
return true;
default:
// global resultCode remains the same
return false;
}
case NO_SUCH_OBJECT:
// global resultCode remains the same
return false;
case REFERRAL:
switch (resultCode.asEnum())
{
case REFERRAL:
return true;
case NO_SUCH_OBJECT:
return false;
default:
// global resultCode remains the same
return true;
}
default:
switch (resultCode.asEnum())
{
case REFERRAL:
return true;
case SUCCESS:
case NO_SUCH_OBJECT:
return false;
default:
// Do nothing (we don't want to override the first error)
return false;
}
}
}
}
/** The backend's baseDN mapped by this object. */
/** The backend associated with the local workflow element. */
/** The set of local backend workflow elements registered with the server. */
/** A lock to guarantee safe concurrent access to the registeredLocalBackends variable. */
/**
* Creates a new instance of the local backend workflow element.
*
* @param baseDN
* the backend's baseDN mapped by this object
* @param backend
* the backend associated to that workflow element
*/
{
}
/**
* Indicates whether the workflow element encapsulates a private local backend.
*
* @return <code>true</code> if the workflow element encapsulates a private
* local backend, <code>false</code> otherwise
*/
public boolean isPrivate()
{
}
/**
* Creates and registers a local backend with the server.
*
* @param baseDN
* the backend's baseDN mapped by this object
* @param backend
* the backend to associate with the local backend workflow element
* @return the existing local backend workflow element if it was already
* created or a newly created local backend workflow element.
*/
{
if (localBackend == null)
{
}
return localBackend;
}
/**
* Removes a local backend that was registered with the server.
*
* @param baseDN
* the identifier of the workflow to remove
*/
{
}
/**
* Removes all the local backends that were registered with the server.
* This function is intended to be called when the server is shutting down.
*/
public static void removeAll()
{
synchronized (registeredLocalBackendsLock)
{
{
}
}
}
/**
* Check if an OID is for a proxy authorization control.
*
* @param oid The OID to check
* @return <code>true</code> if the OID is for a proxy auth v1 or v2 control,
* <code>false</code> otherwise.
*/
{
}
/**
* Removes all the disallowed request controls from the provided operation.
* <p>
* As per RFC 4511 4.1.11, if a disallowed request control is critical, then a
* DirectoryException is thrown with unavailableCriticalExtension. Otherwise,
* if the disallowed request control is non critical, it is removed because we
* do not want the backend to process it.
*
* @param operation
* the operation currently processed
* @throws DirectoryException
* If a disallowed request control is critical, thrown with
* unavailableCriticalExtension. If an error occurred while
* performing the access control check. For example, if an attribute
* could not be decoded. Care must be taken not to expose any
* potentially sensitive information in the exception.
*/
{
{
{
{
continue;
}
{
// As per RFC 4511 4.1.11.
if (control.isCritical())
{
throw new DirectoryException(
}
// We do not want the backend to process this non-critical control, so remove it.
}
}
}
}
/**
* Evaluate all aci and privilege checks for any proxy auth controls.
* This must be done before evaluating all other controls so that their aci
* can then be checked correctly.
*
* @param operation The operation containing the controls
* @throws DirectoryException if a proxy auth control is found but cannot
* be used.
*/
{
{
{
if (isProxyAuthzControl(oid))
{
{
}
else
{
// As per RFC 4511 4.1.11.
if (control.isCritical())
{
throw new DirectoryException(
}
}
}
}
}
}
/**
* Check the requester has the PROXIED_AUTH privilege in order to be able to use a proxy auth control.
*
* @param operation The operation being checked
* @throws DirectoryException If insufficient privileges are detected
*/
private static void checkPrivilegeForProxyAuthControl(Operation operation) throws DirectoryException
{
{
}
}
/**
* Check the requester has the authorization user in scope of proxy aci.
*
* @param operation The operation being checked
* @param authorizationEntry The entry being authorized as (e.g. from a proxy auth control)
* @throws DirectoryException If no proxy permission is allowed
*/
throws DirectoryException
{
{
}
}
/**
* Process the operation control with the given oid if it is a proxy auth control.
*
* Privilege and initial aci checks on the authenticating user are performed. The authenticating
* user must have the proxied-auth privilege, and the authz user must be in the scope of aci
* allowing the proxy right to the authenticating user.
*
* @param operation The operation containing the control(s)
* @param oid The OID of the detected proxy auth control
* @throws DirectoryException
*/
throws DirectoryException
{
final Entry authorizationEntry;
{
final ProxiedAuthV1Control proxyControlV1 = operation.getRequestControl(ProxiedAuthV1Control.DECODER);
// Log usage of legacy proxy authz V1 control.
"obsoleteProxiedAuthzV1Control"));
}
{
final ProxiedAuthV2Control proxyControlV2 = operation.getRequestControl(ProxiedAuthV2Control.DECODER);
}
else
{
return;
}
}
/**
* Returns a new {@link DirectoryException} built from the provided
* resultCodes and messages. Depending on whether ACIs prevent information
* disclosure, the provided resultCode and message will be masked and
* altResultCode and altMessage will be used instead.
*
* @param operation
* the operation for which to check if ACIs prevent information
* disclosure
* @param entry
* the entry for which to check if ACIs prevent information
* disclosure, if null, then a fake entry will be created from the
* entryDN parameter
* @param entryDN
* the entry dn for which to check if ACIs prevent information
* disclosure. Only used if entry is null.
* @param resultCode
* the result code to put on the DirectoryException if ACIs allow
* disclosure. Otherwise it will be put on the DirectoryException as
* a masked result code.
* @param message
* the message to put on the DirectoryException if ACIs allow
* disclosure. Otherwise it will be put on the DirectoryException as
* a masked message.
* @param altResultCode
* the result code to put on the DirectoryException if ACIs do not
* allow disclosing the resultCode.
* @param altMessage
* the result code to put on the DirectoryException if ACIs do not
* allow disclosing the message.
* @return a new DirectoryException containing the provided resultCodes and
* messages depending on ACI allowing disclosure or not
* @throws DirectoryException
* If an error occurred while performing the access control check.
*/
{
{
}
// replacement reason returned to the user
// real underlying reason
return ex;
}
/**
* Sets the provided resultCodes and messages on the provided operation.
* Depending on whether ACIs prevent information disclosure, the provided
* resultCode and message will be masked and altResultCode and altMessage will
* be used instead.
*
* @param operation
* the operation for which to check if ACIs prevent information
* disclosure
* @param entry
* the entry for which to check if ACIs prevent information
* disclosure, if null, then a fake entry will be created from the
* entryDN parameter
* @param entryDN
* the entry dn for which to check if ACIs prevent information
* disclosure. Only used if entry is null.
* @param resultCode
* the result code to put on the DirectoryException if ACIs allow
* disclosure. Otherwise it will be put on the DirectoryException as
* a masked result code.
* @param message
* the message to put on the DirectoryException if ACIs allow
* disclosure. Otherwise it will be put on the DirectoryException as
* a masked message.
* @param altResultCode
* the result code to put on the DirectoryException if ACIs do not
* allow disclosing the resultCode.
* @param altMessage
* the result code to put on the DirectoryException if ACIs do not
* allow disclosing the message.
* @throws DirectoryException
* If an error occurred while performing the access control check.
*/
{
{
}
else
{
// replacement reason returned to the user
// real underlying reason
}
}
/**
* Removes the matchedDN from the supplied operation if ACIs prevent its
* disclosure.
*
* @param operation
* where to filter the matchedDN from
*/
{
{
return;
}
try
{
{
}
}
catch (DirectoryException de)
{
// At this point it is impossible to tell whether the matchedDN can be
// disclosed. It is probably safer to hide it by default.
}
}
/**
* Adds the post-read response control to the response if requested.
*
* @param operation
* The update operation.
* @param postReadRequest
* The request control, if present.
* @param entry
* The post-update entry.
*/
{
if (postReadRequest == null)
{
return;
}
/*
* Virtual and collective attributes are only added to an entry when it is
* read from the backend, not before it is written, so we need to add them
* ourself.
*/
// Even though the associated update succeeded,
// we should still check whether or not we should return the entry.
{
// Filter the entry based on the control's attribute list.
final Entry filteredEntry = fullEntry.filterEntry(postReadRequest.getRequestedAttributes(), false, false, false);
// Strip out any attributes which access control denies access to.
}
}
/**
* Adds the pre-read response control to the response if requested.
*
* @param operation
* The update operation.
* @param preReadRequest
* The request control, if present.
* @param entry
* The pre-update entry.
*/
{
if (preReadRequest == null)
{
return;
}
// Even though the associated update succeeded,
// we should still check whether or not we should return the entry.
{
// Filter the entry based on the control's attribute list.
final Entry filteredEntry = entry.filterEntry(preReadRequest.getRequestedAttributes(), false, false, false);
// Strip out any attributes which access control denies access to.
}
}
private static AccessControlHandler<?> getAccessControlHandler()
{
}
/**
* Registers a local backend with the server.
*
* @param localBackend the local backend to register with the server
*/
{
synchronized (registeredLocalBackendsLock)
{
if (existingLocalBackend == null)
{
}
}
}
/**
* Deregisters a local backend with the server.
*
* @param baseDN
* the identifier of the local backend to remove
*/
{
synchronized (registeredLocalBackendsLock)
{
if (existingLocalBackend != null)
{
}
}
}
/**
* Executes the workflow for an operation.
*
* @param operation
* the operation to execute
* @throws CanceledOperationException
* if this operation should be canceled
*/
switch (operation.getOperationType())
{
case BIND:
break;
case SEARCH:
break;
case ADD:
break;
case DELETE:
break;
case MODIFY:
break;
case MODIFY_DN:
break;
case COMPARE:
break;
case ABANDON:
// There is no processing for an abandon operation.
break;
default:
throw new AssertionError("Attempted to execute an invalid operation type: "
}
}
/**
* Attaches the current local operation to the global operation so that
* operation runner can execute local operation post response later on.
*
* @param <O> subtype of Operation
* @param <L> subtype of LocalBackendOperation
* @param globalOperation the global operation to which local operation
* should be attached to
* @param currentLocalOperation the local operation to attach to the global
* operation
*/
@SuppressWarnings("unchecked")
static <O extends Operation, L> void attachLocalOperation(O globalOperation, L currentLocalOperation)
{
List<?> existingAttachment = (List<?>) globalOperation.getAttachment(Operation.LOCALBACKENDOPERATIONS);
if (existingAttachment != null)
{
// This line raises an unchecked conversion warning.
// There is nothing we can do to prevent this warning
// so let's get rid of it since we know the cast is safe.
}
}
/**
* Provides the workflow element identifier.
*
* @return the workflow element identifier
*/
{
return baseDN;
}
/**
* Gets the backend associated with this local backend workflow
* element.
*
* @return The backend associated with this local backend workflow
* element.
*/
public Backend<?> getBackend()
{
return backend;
}
/**
* Checks if an update operation can be performed against a backend. The
* operation will be rejected based on the server and backend writability
* modes.
*
* @param backend
* The backend handling the update.
* @param op
* The update operation.
* @param entryDN
* The name of the entry being updated.
* @param serverMsg
* The message to log if the update was rejected because the server
* is read-only.
* @param backendMsg
* The message to log if the update was rejected because the backend
* is read-only.
* @throws DirectoryException
* If the update operation has been rejected.
*/
throws DirectoryException
{
if (!backend.isPrivateBackend())
{
}
}
{
switch (writabilityMode)
{
case DISABLED:
case INTERNAL_ONLY:
{
}
}
}
/**
* Executes the supplied operation.
*
* @param operation
* the operation to execute
* @param entryDN
* the entry DN whose backend will be used
* @return true if the operation successfully executed, false otherwise
* @throws CanceledOperationException
* if this operation should be cancelled.
*/
{
{
// We have found no backend for the requested base DN,
// just return a no such entry result code and stop the processing.
if (operation instanceof AbstractOperation)
{
}
return false;
}
{
}
else
{
}
return true;
}
{
{
{
return workflow;
}
}
return null;
}
/**
* Executes an operation on the root DSE entry.
*
* @param operation
* the operation to execute
* @param workflow
* the workflow where to execute the operation
* @throws CanceledOperationException
* if this operation should be cancelled.
*/
throws CanceledOperationException
{
{
}
else
{
}
}
/**
* Executes a search operation on the the root DSE entry.
*
* @param searchOp
* the operation to execute
* @param workflow
* the workflow where to execute the operation
* @throws CanceledOperationException
* if this operation should be cancelled.
*/
throws CanceledOperationException
{
// Keep a the original search scope because we will alter it in the operation
// Search base?
// The root DSE entry itself is never returned unless the operation
// is a search base on the null suffix.
{
return;
}
// Create a workflow result code in case we need to perform search in
// subordinate workflows.
// The search scope is not 'base', so let's do a search on all the public
// naming contexts with appropriate new search scope and new base DN.
{
// We have to change the operation request base DN to match the
// subordinate workflow base DN. Otherwise the workflow will
// return a no such entry result code as the operation request
// base DN is a superior of the workflow base DN!
// Set the new request base DN then do execute the operation
// in the naming context workflow.
if (sendReferenceEntry)
{
// TODO jdemendi - turn a referral result code into a reference entry
// and send the reference entry to the client application
}
}
// Now restore the original request base DN and original search scope
// If the result code is still uninitialized (ie no naming context),
// we should return NO_SUCH_OBJECT
// Set the operation result code and error message
}
{
{
}
return results;
}
throws CanceledOperationException
{
// For subtree search operation we need to go through the subordinate nodes.
{
}
}
/**
* Executes a search operation on the subordinate workflows.
*
* @param searchOp
* the search operation to execute
* @param workflow
* the workflow element
* @throws CanceledOperationException
* if this operation should be canceled.
*/
private static void executeSearchOnSubordinates(SearchOperation searchOp, LocalBackendWorkflowElement workflow)
throws CanceledOperationException {
// If the scope of the search is 'base' then it's useless to search
// in the subordinate workflows.
{
return;
}
// Elaborate the new search scope before executing the search operation
// in the subordinate workflows.
// Let's search in the subordinate workflows.
SearchResultCode searchResultCode = new SearchResultCode(searchOp.getResultCode(), searchOp.getErrorMessage());
{
// We have to change the operation request base DN to match the
// subordinate workflow base DN. Otherwise the workflow will
// return a no such entry result code as the operation request
// base DN is a superior of the subordinate workflow base DN.
// If the new search scope is 'base' and the search base DN does not
// map the subordinate workflow then skip the subordinate workflow.
{
continue;
}
// If the request base DN is not a subordinate of the subordinate
// workflow base DN then do not search in the subordinate workflow.
{
continue;
}
// Set the new request base DN and do execute the
// operation in the subordinate workflow.
if (sendReferenceEntry)
{
// TODO jdemendi - turn a referral result code into a reference entry
// and send the reference entry to the client application
}
}
// Now we are done with the operation, let's restore the original
// base DN and search scope in the operation.
// Update the operation result code and error message
}
private static Collection<LocalBackendWorkflowElement> getSubordinates(LocalBackendWorkflowElement workflow)
{
{
{
{
}
}
}
return results;
}
/**
* Elaborates a new search scope according to the current search scope. The
* new scope is intended to be used for searches on subordinate workflows.
*
* @param currentScope
* the current search scope
* @return the new scope to use for searches on subordinate workflows,
* <code>null</code> when current scope is 'base'
*/
{
switch (currentScope.asEnum())
{
case BASE_OBJECT:
return null;
case SINGLE_LEVEL:
return SearchScope.BASE_OBJECT;
case SUBORDINATES:
case WHOLE_SUBTREE:
return SearchScope.WHOLE_SUBTREE;
default:
return currentScope;
}
}
/** {@inheritDoc} */
{
return getClass().getSimpleName()
+ " backend=" + this.backend
+ " baseDN=" + this.baseDN;
}
}