AciHandler.java revision ca669ae54f86dbeea277280690584d9f591c7571
/*
* 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
* Portions Copyright 2013 Manuel Gaupp
*/
/**
* The AciHandler class performs the main processing for the dseecompat package.
*/
public final class AciHandler extends
{
/**
* String used to indicate that the evaluating ACI had a all
* operational attributes targetattr match (targetattr="+").
*/
/**
* String used to indicate that the evaluating ACI had a all user
* attributes targetattr match (targetattr="*").
*/
/**
* String used to save the original authorization entry in an
* operation attachment if a proxied authorization control was seen.
*/
/** Attribute type corresponding to "aci" attribute. */
static AttributeType aciType;
/** Attribute type corresponding to global "ds-cfg-global-aci" attribute. */
static AttributeType globalAciType;
/** Attribute type corresponding to "debugsearchindex" attribute. */
private static AttributeType debugSearchIndex;
/** DN corresponding to "debugsearchindex" attribute type. */
private static DN debugSearchIndexDN;
/**
* Attribute type corresponding to the "ref" attribute type. Used in
* the search reference access check.
*/
private static AttributeType refAttrType;
static
{
initStatics();
}
/**
* We initialize these for each new AciHandler so that we can clear out the
* stale references that can occur during an in-core restart.
*/
private static void initStatics()
{
try
{
}
catch (DirectoryException ex)
{
// Should never happen.
}
}
{
{
}
return attrType;
}
/** The list that holds that ACIs keyed by the DN of the entry holding the ACI. */
/**
* The listener that handles ACI changes caused by LDAP operations,
* ACI decode failure alert logging and backend initialization ACI list adjustment.
*/
private AciListenerManager aciListenerMgr;
/** Creates a new DSEE-compatible access control handler. */
public AciHandler()
{
// No implementation required. All initialization should be done in
// the intializeAccessControlHandler method.
}
/** {@inheritDoc} */
{
// Proxy access check has already been done for this entry in the
// maySend method, set the seen flag to true to bypass any proxy check.
container.setSeenEntry(true);
if (!skipCheck)
{
}
{
}
}
/** {@inheritDoc} */
public void finalizeAccessControlHandler()
{
}
/** {@inheritDoc} */
public void initializeAccessControlHandler(
{
initStatics();
}
/** {@inheritDoc} */
throws DirectoryException
{
if (!skipAccessCheck(op))
{
ACI_READ | ACI_CONTROL);
if (!accessAllowed(container))
{
return false;
}
}
{
}
{
if (control instanceof LDAPControl)
{
}
else
{
}
}
return true;
}
/** {@inheritDoc} */
{
if (skipAccessCheck(operation))
{
return true;
}
final AciContainer container =
return accessAllowed(container);
}
/** {@inheritDoc} */
throws DirectoryException
{
// LDAP add needs a verify ACI syntax step in case any
// "aci" attribute types are being added.
}
/** {@inheritDoc} */
{
// Not planned to be implemented.
return true;
}
/**
* Check access on compare operations. Note that the attribute type is
* unavailable at this time, so this method partially parses the raw
* attribute string to get the base attribute type. Options are
* ignored.
*
* @param operation
* The compare operation to check access on.
* @return True if access is allowed.
*/
{
if (semicolonPosition > 0)
{
baseName =
}
else
{
}
}
/**
* Check access on delete operations.
*
* @param operation
* The delete operation to check access on.
* @return True if access is allowed.
*/
{
}
/**
* Checks access on a modifyDN operation.
*
* @param operation
* The modifyDN operation to check access on.
* @return True if access is allowed.
*/
{
if (skipAccessCheck(operation))
{
return true;
}
// If this is a modifyDN move to a new superior, then check if the
// superior DN has import access.
if (newSuperiorDN != null
{
return false;
}
// Perform the RDN access checks.
// If this is a modifyDN move to a new superior, then check if the
// original entry DN has export access.
{
{
// The RDNs are not equal, skip the proxy check since it was
// already performed in the aciCheckRDNs call above.
container.setSeenEntry(true);
}
return accessAllowed(container);
}
return rdnChangesAllowed;
}
/** {@inheritDoc} */
throws DirectoryException
{
}
/** {@inheritDoc} */
{
// Not planned to be implemented.
return true;
}
/** {@inheritDoc} */
{
if (skipAccessCheck(operation))
{
return true;
}
}
/** {@inheritDoc} */
{
if (skipAccessCheck(proxyUser))
{
return true;
}
final AuthenticationInfo authInfo =
.getName()));
final AciContainer container =
return accessAllowedEntry(container);
}
/** {@inheritDoc} */
{
if (skipAccessCheck(operation))
{
return true;
}
final AttributeBuilder builder =
// Load the values, a bind rule might want to evaluate them.
{
}
final AciContainer container =
return accessAllowed(container);
}
/** {@inheritDoc} */
{
if (skipAccessCheck(operation))
{
return true;
}
if (operation instanceof SearchOperation)
{
try
{
{
return false;
}
}
catch (DirectoryException ex)
{
return false;
}
}
if (!accessAllowedEntry(container))
{
return false;
}
if (!container.hasEvalUserAttributes())
{
}
if (!container.hasEvalOpAttributes())
{
}
return true;
}
/**
* Check access using the specified container. This container will
* have all of the information to gather applicable ACIs and perform
* evaluation on them.
*
* @param container
* An ACI operation container which has all of the
* information needed to check access.
* @return True if access is allowed.
*/
{
// For ACI_WRITE_ADD and ACI_WRITE_DELETE set the ACI_WRITE
// right.
{
}
// Check if the ACI_SELF right needs to be set (selfwrite right).
// Only done if the right is ACI_WRITE, an attribute value is set
// and that attribute value is a DN.
{
try
{
// Have a valid DN, compare to clientDN to see if the ACI_SELF
// right should be set.
{
}
}
catch (DirectoryException ex)
{
// Log a message and keep going.
}
}
// Check proxy authorization only if the entry has not already been
// processed (working on a new entry). If working on a new entry,
// then only do a proxy check if the right is not set to ACI_PROXY
// and the proxied authorization control has been decoded.
if (!container.hasSeenEntry())
{
{
// Save the current rights so they can be put back if on success.
// Switch to the original authorization entry, not the proxied one.
if (!accessAllowed(container))
{
return false;
}
// Access is ok, put the original rights back.
// Put the proxied authorization entry back to the current
// authorization entry.
container.useOrigAuthorizationEntry(false);
}
// Set the seen flag so proxy processing is not performed for this
// entry again.
container.setSeenEntry(true);
}
// First get all allowed candidate ACIs.
/*
* Create an applicable list of ACIs by target matching each
* candidate ACI against the container's target match view.
*/
// Evaluate the applicable list.
// Build summary string if doing geteffectiverights eval.
{
}
return ret;
}
/*
* TODO Evaluate performance of this method. TODO Evaluate security
* concerns of this method. Logic from this method taken almost
* directly from DS6 implementation. I find the work done in the
* accessAllowedEntry method, particularly with regard to the entry
* test evaluation, to be very confusing and potentially pretty
* inefficient. I'm also concerned that the "return "true" inside the
* for loop could potentially allow access when it should be denied.
*/
/**
* Check if access is allowed on an entry. Access is checked by
* iterating through each attribute of an entry, starting with the
* "objectclass" attribute type. If access is allowed on the entry
* based on one of it's attribute types, then a possible second access
* check is performed. This second check is only performed if an entry
* test ACI was found during the earlier successful access check. An
* entry test ACI has no "targetattrs" keyword, so allowing access
* based on an attribute type only would be incorrect.
*
* @param container
* ACI search container containing all of the information
* needed to check access.
* @return True if access is allowed.
*/
{
// set flag that specifies this is the first attribute evaluated
// in the entry
container.setIsFirstAttribute(true);
{
/*
* Check if access is allowed. If true, then check to see if an
* entry test rule was found (no targetattrs) during target match
* evaluation. If such a rule was found, set the current attribute
* type to "null" and check access again so that rule is applied.
*/
if (accessAllowed(container))
{
if (container.hasEntryTestRule())
{
{
/*
* If we failed because of a deny permission-bind rule, we need to
* stop and return false.
* If we failed because there was no explicit allow rule, then we
* grant implicit access to the entry.
*/
return false;
}
}
return true;
}
}
return false;
}
/**
* Performs an access check against all of the attributes of an entry. The
* attributes that fail access are removed from the entry. This method
* performs the processing needed for the filterEntry method processing.
*
* @param container
* The search or compare container which has all of the information
* needed to filter the attributes for this entry.
* @param filteredEntry
* The partially filtered search result entry being returned to the
* client.
*/
{
{
{
continue;
}
{
continue;
}
if (!accessAllowed(container))
{
}
}
}
/**
* Checks to see if a LDAP modification is allowed access.
*
* @param container
* The structure containing the LDAP modifications
* @param operation
* The operation to check modify privileges on. operation to
* check and the evaluation context to apply the check
* against.
* @param skipAccessCheck
* True if access checking should be skipped.
* @return True if access is allowed.
* @throws DirectoryException
* If a modified ACI could not be decoded.
*/
throws DirectoryException
{
for (Modification m : modifications)
{
/*
* Check that the operation has modify privileges if it contains
* an "aci" attribute type.
*/
{
return false;
}
// This access check handles the case where all attributes of this
// type are being replaced or deleted. If only a subset is being
// deleted than this access check is skipped.
/*
* Check if we have rights to delete all values of an attribute
* type in the resource entry.
*/
{
{
{
for (ByteString v : a)
{
{
return false;
}
}
}
}
}
{
for (ByteString v : modAttr)
{
switch (m.getModificationType().asEnum())
{
case ADD:
case REPLACE:
{
return false;
}
break;
case DELETE:
{
return false;
}
break;
case INCREMENT:
if (modifiedAttrs != null)
{
{
{
{
return false;
}
}
}
}
break;
}
/*
* Check if the modification type has an "aci" attribute type.
* If so, check the syntax of that attribute value. Fail the
* the operation if the syntax check fails.
*/
{
try
{
// A global ACI needs a NULL DN, not the DN of the
// modification.
{
}
// validate ACI syntax
}
catch (AciException ex)
{
}
}
}
}
}
return true;
}
/**
* Perform all needed RDN checks for the modifyDN operation. The old RDN is
* not equal to the new RDN. The access checks are:
* <ul>
* <li>Verify WRITE access to the original entry.</li>
* <li>Verify WRITE_ADD access on each RDN component of the new RDN. The
* WRITE_ADD access is used because this access could be restricted by the
* targattrfilters keyword.</li>
* <li>If the deleteOLDRDN flag is set, verify WRITE_DELETE access on the old
* RDN. The WRITE_DELETE access is used because this access could be
* restricted by the targattrfilters keyword.
* <li>
* </ul>
*
* @param operation
* The ModifyDN operation class containing information to check
* access on.
* @param oldRDN
* The old RDN component.
* @param newRDN
* The new RDN component.
* @return True if access is allowed.
*/
{
.getOriginalEntry());
if (!accessAllowed(container))
{
return false;
}
{
}
return ret;
}
/**
* Check access on the new superior entry if it exists. If superiordn is null,
* the entry does not exist or the DN cannot be locked then false is returned.
*
* @param superiorDN
* The DN of the new superior entry.
* @param op
* The modifyDN operation to check access on.
* @return True if access is granted to the new superior entry.
*/
{
{
return false;
}
try
{
if (superiorEntry != null)
{
return accessAllowed(container);
}
return false;
}
catch (DirectoryException ex)
{
return false;
}
finally
{
}
}
/**
* Check access on each attribute-value pair component of the
* specified RDN. There may be more than one attribute-value pair if
* the RDN is multi-valued.
*
* @param right
* The access right to check for.
* @param rdn
* The RDN to examine the attribute-value pairs of.
* @param container
* The container containing the information needed to
* evaluate the specified RDN.
* @return True if access is allowed for all attribute-value pairs.
*/
{
for (int i = 0; i < numAVAs; i++)
{
if (!accessAllowed(container))
{
return false;
}
}
return true;
}
/**
* Creates the allow and deny ACI lists based on the provided target
* match context. These lists are stored in the evaluation context.
*
* @param candidates
* List of all possible ACI candidates.
* @param targetMatchCtx
* Target matching context to use for testing each ACI.
*/
{
{
{
{
}
{
}
}
{
}
}
}
/**
* Gathers all of the attribute types in an entry along with the
* "objectclass" attribute type in a List. The "objectclass" attribute
* is added to the list first so it is evaluated first.
*
* @param e
* Entry to gather the attributes for.
* @return List containing the attribute types.
*/
{
/*
* When a search is not all attributes returned, the "objectclass"
* attribute type is missing from the entry.
*/
{
}
return typeList;
}
/**
* Check access using the accessAllowed method. The LDAP add, compare,
* modify and delete operations use this function. The other supported
* LDAP operations have more specialized checks.
*
* @param container
* The container containing the information needed to
* evaluate this operation.
* @param operation
* The operation being evaluated.
* @return True if this operation is allowed access.
*/
{
}
/**
* Check if the specified attribute type is a DN by checking if its
* syntax OID is equal to the DN syntax OID.
*
* @param attribute
* The attribute type to check.
* @return True if the attribute type syntax OID is equal to a DN
* syntax OID.
*/
{
}
/**
* Process all ACIs under the "cn=config" naming context and adds them
* to the ACI list cache. It also logs messages about the number of
* ACIs added to the cache. This method is called once at startup. It
* will put the server in lockdown mode if needed.
*
* @throws InitializationException
* If there is an error searching for the ACIs in the naming
* context.
*/
private void processConfigAcis() throws InitializationException
{
{
try
{
{
continue;
}
}
catch (Exception e)
{
logger.traceException(e);
// FIXME -- Is there anything that we need to do here?
continue;
}
try {
SearchRequest request = newSearchRequest(baseDN, SearchScope.WHOLE_SUBTREE, "aci=*").addAttribute("aci");
{
int validAcis =
if (!failedACIMsgs.isEmpty())
{
}
}
}
catch (Exception e)
{
throw new InitializationException(message, e);
}
}
}
/**
* Process all global ACI attribute types found in the configuration
* entry and adds them to that ACI list cache. It also logs messages
* about the number of ACI attribute types added to the cache. This
* method is called once at startup. It also will put the server into
* lockdown mode if needed.
*
* @param configuration
* The config handler containing the ACI configuration
* information.
* @throws InitializationException
* If there is an error reading the global ACIs from the
* configuration entry.
*/
private void processGlobalAcis(
throws InitializationException
{
try
{
if (globalAcis != null)
{
}
}
catch (Exception e)
{
logger.traceException(e);
throw new InitializationException(
}
}
/**
* Check to see if the specified entry has the specified privilege.
*
* @param e
* The entry to check privileges on.
* @return {@code true} if the entry has the specified privilege, or
* {@code false} if not.
*/
private boolean skipAccessCheck(Entry e)
{
}
/**
* Check to see if the client entry has BYPASS_ACL privileges for this
* operation.
*
* @param operation
* The operation to check privileges on.
* @return True if access checking can be skipped because the
* operation client connection has BYPASS_ACL privileges.
*/
{
}
/**
* Performs the test of the deny and allow access lists using the
* provided evaluation context. The deny list is checked first.
*
* @param evalCtx
* The evaluation context to use.
* @return True if access is allowed.
*/
{
&& (!evalCtx.isGetEffectiveRightsEval()
|| !evalCtx.isTargAttrFilterMatchAciEmpty()))
{
// If allows list is empty and not doing geteffectiverights return false.
return false;
}
{
// Failure could be returned if a system limit is hit or
// search fails
{
return false;
}
{
{
continue;
}
return false;
}
}
{
{
{
continue;
}
return true;
}
}
// Nothing matched fall through.
return false;
}
{
return evalCtx.isGetEffectiveRightsEval()
// Iterate to next only if ACI contains a targattrfilters keyword.
}
/**
* Test the attribute types of the search filter for access. This
* method supports the search right.
*
* @param container
* The container used in the access evaluation.
* @param filter
* The filter to check access on.
* @return True if all attribute types in the filter have access.
* @throws DirectoryException
* If there is a problem matching the entry using the
* provided filter.
*/
throws DirectoryException
{
// If the resource entry has a dn equal to "cn=debugsearch" and it
// contains the special attribute type "debugsearchindex", then the
// resource entry is a pseudo entry created for debug purposes.
// Return true if that is the case.
{
return true;
}
switch (filter.getFilterType())
{
case AND:
case OR:
{
{
if (!testFilter(container, f))
{
return false;
}
}
break;
}
case NOT:
{
}
default:
{
return accessAllowed(container);
}
}
return true;
}
/**
* Evaluate an entry to be added to see if it has any "aci" attribute
* type. If it does, examines each "aci" attribute type value for
* syntax errors. All of the "aci" attribute type values must pass
* syntax check for the add operation to proceed. Any entry with an
* "aci" attribute type must have "modify-acl" privileges.
*
* @param entry
* The entry to be examined.
* @param operation
* The operation to to check privileges on.
* @param clientDN
* The authorization DN.
* @return True if the entry has no ACI attributes or if all of the
* "aci" attributes values pass ACI syntax checking.
* @throws DirectoryException
* If a modified ACI could not be decoded.
*/
{
{
/*
* Check that the operation has "modify-acl" privileges since the
* entry to be added has an "aci" attribute type.
*/
{
return false;
}
{
{
try
{
// validate ACI syntax
}
catch (AciException ex)
{
throw new DirectoryException(
}
}
}
}
return true;
}
}