AciHandler.java revision 03839fc8bfcf7f63ca2b9d3a48faabf94642b00b
/*
* 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
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
* add the following below this CDDL HEADER, with the fields enclosed
* by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Portions Copyright 2007 Sun Microsystems, Inc.
*/
/**
* The AciHandler class performs the main processing for the dseecompat package.
*/
public class AciHandler
{
/**
* The tracer object for the debug logger.
*/
/**
* 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;
/**
* 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.
*/
static AttributeType debugSearchIndex;
/*
* DN corresponding to "debugsearchindex" attribute type.
*/
static DN debugSearchIndexDN;
/**
* String used to save the original authorization entry in an operation
* attachment if a proxied authorization control was seen.
*/
/**
* String used to save a resource entry containing all the attributes in
* the SearchOperation attachment list. This is only used during
* geteffectiverights read right processing when all of an entry'ss
* attributes need to examined.
*/
/**
* String used to indicate that the evaluating ACI had a all user attributes
* targetattr match (targetattr="*").
*/
/**
* String used to indicate that the evaluating ACI had a all operational
* attributes targetattr match (targetattr="+").
*/
static {
{
}
if((globalAciType =
{
}
if((debugSearchIndex =
{
}
try {
} catch (DirectoryException ex) {
//Should never happen.
}
}
/**
* Creates a new DSEE-compatible access control handler.
*/
public AciHandler()
{
// No implementation required. All initialization should be done in the
// intializeAccessControlHandler method.
}
/**
* {@inheritDoc}
*/
@Override()
public void initializeAccessControlHandler(
{
}
/**
* {@inheritDoc}
*/
@Override()
public void finalizeAccessControlHandler()
{
// No implementation required.
}
/**
* 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 {
int msgID;
try {
{
}
attVals);
if(!failedACIMsgs.isEmpty())
} else {
}
} catch (Exception e) {
if (debugEnabled())
}
}
/**
* 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
{
int msgID = MSGID_ACI_ADD_LIST_NO_ACIS;
} else {
int validAcis =
if(!failedACIMsgs.isEmpty())
int msgID = MSGID_ACI_ADD_LIST_ACIS;
}
} catch (DirectoryException e) {
}
}
/**
* 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.
*/
boolean skipAccessCheck) {
for(Modification m : modifications) {
/*
* Check that the operation has modify privileges if
* it contains an "aci" attribute type.
*/
if (!operation.getClientConnection().
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 (AttributeValue v : a.getValues()) {
if(!skipAccessCheck &&
return false;
}
}
}
}
switch (m.getModificationType())
{
case ADD:
case REPLACE:
return false;
break;
case DELETE:
return false;
break;
case INCREMENT:
modAttr.getOptions());
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.
} catch (AciException ex) {
ex.getMessage());
return false;
}
}
}
}
}
return true;
}
/**
* 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.
*/
//If allows list is empty and not doing geteffectiverights return
//false.
return false;
}
evalCtx.setDenyEval(true);
//Failure could be returned if a system limit is hit or
//search fails
return false;
if(evalCtx.isGetEffectiveRightsEval() &&
//Iterate to next only if deny ACI contains a targattrfilters
//keyword.
continue;
return false;
} else {
return false;
}
}
}
//Now check the allows -- flip the deny flag to false first.
evalCtx.setDenyEval(false);
if(evalCtx.isGetEffectiveRightsEval() &&
//Iterate to next only if deny ACI contains a targattrfilters
//keyword.
continue;
return true;
} else {
return true;
}
}
}
//Nothing matched fall through.
return false;
}
/**
* 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.
*/
{
}
}
}
}
}
/**
* 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.
*/
return operation.getClientConnection().
}
/**
* 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.
int msgID = MSGID_ACI_NOT_VALID_DN;
}
}
//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()) {
if(container.isProxiedAuthorization() &&
//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;
}
/**
* 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.
*/
}
/**
* 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.
* @return The entry to send back to the client, minus any attribute
* types that failed access check.
*/
private SearchResultEntry
continue;
continue;
if(!accessAllowed(container))
}
return container.getSearchResultEntry();
}
/**
* 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;
}
/*
* 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.
*/
boolean ret=false;
//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(!accessAllowed(container)) {
/*
* If we failed because of a deny permission-bind rule,
* we need to stop and return false.
*/
if(container.isDenyEval()) {
return false;
}
/*
* If we failed because there was no explicit
* allow rule, then we grant implicit access to the
* entry.
*/
}
}
return true;
}
}
return ret;
}
/**
* 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.
*/
private boolean
throws DirectoryException {
boolean ret=true;
//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 psudo 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: {
ret=false;
ret=true;
if(ret)
break;
}
default: {
}
}
return ret;
}
/**
* 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 operationContainer The container containing the information
* needed to evaluate this operation.
* @param operation The operation being evaluated.
* @return True if this operation is allowed access.
*/
}
/**
* 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.
*/
private boolean
/*
* Check that the operation has "modify-acl" privileges since the
* entry to be added has an "aci" attribute type.
*/
if (!operation.getClientConnection().
return false;
}
{
{
try {
} catch (AciException ex) {
int msgID = MSGID_ACI_ADD_FAILED_DECODE;
ex.getMessage());
return false;
}
}
}
}
return true;
}
/**
* Check access on add operations.
*
* @param operation The add operation to check access on.
* @return True if access is allowed.
*/
//LDAP add needs a verify ACI syntax step in case any
//"aci" attribute types are being added.
if(ret)
return ret;
}
/**
* 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
if((attributeType =
}
/**
* Check access on delete operations.
*
* @param operation The delete operation to check access on.
* @return True if access is allowed.
*/
}
/**
* Check access on modify operations.
*
* @param operation The modify operation to check access on.
* @return True if access is allowed.
*/
}
/**
* Checks access on a search operation.
* @param operation The search operation class containing information to
* check the access on.
* @param entry The entry to evaluate access.
* @return True if access is allowed.
*/
public boolean
(ACI_SEARCH), entry);
boolean ret;
try {
} catch (DirectoryException ex) {
ret=false;
}
if (ret) {
if(ret) {
}
}
}
//Save a copy of the full resource entry for possible
//userattr bind rule or geteffectiveright's evaluations in the filterEnty
//method.
return ret;
}
/*
* TODO Rename this method. Needs to be changed in SearchOperation.
*
* I find the name of the filterEntry method to be misleading because
* it works on a search operation but has nothing to do with the search
* filter. Something like "removeDisallowedAttributes" would be clearer.
*/
/**
* Checks access on each attribute in an entry. It removes those attributes
* that fail access check.
*
* @param operation The search operation class containing information to
* check access on.
* @param entry The entry containing the attributes.
* @return The entry to return minus filtered attributes.
*/
//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.
operationContainer.setSeenEntry(true);
if(!skipCheck) {
} else
}
return returnEntry;
}
/**
* Perform all needed RDN checks for the modifyDN operation. The old RDN is
* not equal to the new RDN. The access checks are:
*
* - Verify WRITE access to the original entry.
* - Verfiy 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.
* - 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.
*
* @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.
*/
boolean ret;
if(ret)
ret =
}
return ret;
}
/**
* 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.
*/
boolean ret=false;
for (int i = 0; i < numAVAs; i++){
break;
}
return ret;
}
/**
* Check access on the new superior entry if it exists. If 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.
* @throws DirectoryException If a problem occurs while trying to
* retrieve the new superior entry.
*/
throws DirectoryException {
boolean ret=false;
for (int i=0; i < 3; i++) {
break;
}
return false;
}
try {
if(superiorEntry!= null) {
}
} finally {
}
return ret;
}
/**
* Checks access on a modifyDN operation.
*
* @param operation The modifyDN operation to check access on.
* @return True if access is allowed.
*
*/
boolean ret=true;
if(!skipAccessCheck(operation)) {
//If this is a modifyDN move to a new superior, then check if the
//superior DN has import accesss.
try {
} catch (DirectoryException ex) {
ret=false;
}
}
//Perform the RDN access checks only if the RDNs are not equal.
//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.
if(!rdnEquals)
operationContainer.setSeenEntry(true);
}
}
return ret;
}
/**
* {@inheritDoc}
*/
boolean ret;
(ACI_READ | ACI_CONTROL));
}
try {
} catch (LDAPException le) {
ret=false;
}
}
return ret;
}
/**
* {@inheritDoc}
*/
boolean ret;
}
return ret;
}
//Not planned to be implemented methods.
/**
* {@inheritDoc}
*/
//TODO: Deferred.
return true;
}
/**
* {@inheritDoc}
*/
//Not planned to be implemented.
return true;
}
/**
* {@inheritDoc}
*/
//Not planned to be implemented.
return true;
}
}