/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
* add the following below this CDDL HEADER, with the fields enclosed
* by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2008 Sun Microsystems, Inc.
* Portions Copyright 2011 ForgeRock AS
*/
package org.opends.server.authorization.dseecompat;
import org.opends.server.protocols.ldap.LDAPClientConnection;
import org.opends.server.types.*;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.Group;
import org.opends.server.core.AddOperationBasis;
import org.opends.server.core.SearchOperation;
import org.opends.server.types.Operation;
import java.net.InetAddress;
import java.security.cert.Certificate;
import java.util.LinkedList;
import java.util.List;
import java.util.HashMap;
import static org.opends.server.authorization.dseecompat.Aci.*;
import static org.opends.server.authorization.dseecompat.AciHandler.*;
import org.opends.server.controls.GetEffectiveRightsRequestControl;
import static org.opends.server.util.ServerConstants.OID_GET_EFFECTIVE_RIGHTS;
/**
* The AciContainer class contains all of the needed information to perform
* both target match and evaluate an ACI. Target matching is the process
* of testing if an ACI is applicable to an operation, and evaluation is
* the actual access evaluation of the ACI.
*/
public abstract class AciContainer
implements AciTargetMatchContext, AciEvalContext {
/*
* The allow and deny lists.
*/
private LinkedList<Aci> denyList, allowList;
/*
* The attribute type in the resource entry currently being evaluated.
*/
private AttributeType attributeType;
/*
* The attribute type value in the resource entry currently being
* evaluated.
*/
private AttributeValue attributeValue;
/*
* True if this is the first attribute type in the resource entry being
* evaluated.
*/
private boolean isFirst = false;
/*
* True if an entry test rule was seen during target matching of an ACI
* entry. A entry test rule is an ACI with targetattrs target keyword.
*/
private boolean isEntryTestRule = false;
/*
* True if the evaluation of an ACI is from the deny list.
*/
private boolean isDenyEval;
/*
* True if the evaluation is a result of an LDAP add operation.
*/
private boolean isAddOp=false;
/*
* The right mask to use in the evaluation of the LDAP operation.
*/
private int rightsMask;
/*
* The entry being evaluated (resource entry).
*/
private Entry resourceEntry;
/*
* The client connection information.
*/
private final ClientConnection clientConnection;
/*
* The operation being evaluated.
*/
private final Operation operation;
/*
* True if a targattrfilters match was found.
*/
private boolean targAttrFiltersMatch=false;
/*
* The authorization entry currently being evaluated. If proxied
* authorization is being used and the handler is doing a proxy access
* check, then this entry will switched to the original authorization entry
* rather than the proxy ID entry. If the check succeeds, it will be
* switched back for non-proxy access checking. If proxied authentication
* is not being used then this entry never changes.
*/
private Entry authorizationEntry;
/*
* Used to save the current authorization entry when the authorization
* entry is switched during a proxy access check.
*/
private final Entry saveAuthorizationEntry;
/*
* This entry is only used if proxied authorization is being used. It is
* the original authorization entry before the proxied authorization change.
*/
private Entry origAuthorizationEntry=null;
/*
* True if proxied authorization is being used.
*/
private boolean proxiedAuthorization=false;
/*
* Used by proxied authorization processing. True if the entry has already
* been processed by an access proxy check. Some operations might perform
* several access checks on the same entry (modify DN), this
* flag is used to bypass the proxy check after the initial evaluation.
*/
private boolean seenEntry=false;
/*
* True if geteffectiverights evaluation is in progress.
*/
private boolean isGetEffectiveRightsEval=false;
/*
* True if the operation has a geteffectiverights control.
*/
private boolean hasGetEffectiveRightsControl=false;
/*
* The geteffectiverights authzID in DN format.
*/
private DN authzid=null;
/*
* True if the authZid should be used as the client DN, only used in
* geteffectiverights evaluation.
*/
private boolean useAuthzid=false;
/*
* The list of specific attributes to get rights for, in addition to
* any attributes requested in the search.
*/
private List<AttributeType> specificAttrs=null;
/*
* Table of ACIs that have targattrfilter keywords that matched. Used
* in geteffectiverights attributeLevel write evaluation.
*/
private final HashMap<Aci,Aci> targAttrFilterAcis=new HashMap<Aci, Aci>();
/*
* The name of a ACI that decided an evaluation and contained a
* targattrfilter keyword. Used in geteffectiverights attributeLevel
* write evaluation.
*/
private String targAttrFiltersAciName=null;
/*
* Value that is used to store the allow/deny result of a deciding ACI
* containing a targattrfilter keyword. Used in geteffectiverights
* attributeLevel write evaluation.
*/
private int targAttrMatch=0;
/*
* The ACI that decided the last evaluation. Used in geteffectiverights
* loginfo processing.
*/
private Aci decidingAci=null;
/*
* The reason the last evaluation decision was made. Used both
* in geteffectiverights loginfo processing and attributeLevel write
* evaluation.
*/
private EnumEvalReason evalReason=null;
/*
* A summary string holding the last evaluation information in textual
* format. Used in geteffectiverights loginfo processing.
*/
private String summaryString=null;
/*
* Flag used to determine if ACI all attributes target matched.
*/
private int evalAllAttributes=0;
/*
* String used to hold a control OID string.
*/
private String controlOID;
/*
* String used to hold an extended operation OID string.
*/
private String extOpOID;
/*
* AuthenticationInfo class to use.
*/
private AuthenticationInfo authInfo;
/**
* This constructor is used by all currently supported LDAP operations
* except the generic access control check that can be used by
* plugins.
*
* @param operation The Operation object being evaluated and target
* matching.
*
* @param rights The rights array to use in evaluation and target matching.
*
* @param entry The current entry being evaluated and target matched.
*/
protected AciContainer(Operation operation, int rights, Entry entry) {
this.resourceEntry=entry;
this.operation=operation;
this.clientConnection=operation.getClientConnection();
if(operation instanceof AddOperationBasis)
this.isAddOp=true;
this.authInfo = clientConnection.getAuthenticationInfo();
//If the proxied authorization control was processed, then the operation
//will contain an attachment containing the original authorization entry.
this.origAuthorizationEntry =
(Entry) operation.getAttachment(ORIG_AUTH_ENTRY);
if(origAuthorizationEntry != null)
this.proxiedAuthorization=true;
this.authorizationEntry=operation.getAuthorizationEntry();
//The ACI_READ right at constructor time can only be the result of the
//AciHandler.filterEntry method. This method processes the
//geteffectiverights control, so it needs to check for it. There are
//two other checks done, because the resource entry passed to that method
//is filtered (it may not contain enough attribute information
//to evaluate correctly). See the the comments below.
if (rights == ACI_READ) {
//Checks if a geteffectiverights control was sent and
//sets up the structures needed.
GetEffectiveRightsRequestControl getEffectiveRightsControl =
(GetEffectiveRightsRequestControl)
operation.getAttachment(OID_GET_EFFECTIVE_RIGHTS);
if (getEffectiveRightsControl != null
&& operation instanceof SearchOperation)
{
hasGetEffectiveRightsControl = true;
if (getEffectiveRightsControl.getAuthzDN() == null)
this.authzid = getClientDN();
else this.authzid = getEffectiveRightsControl.getAuthzDN();
this.specificAttrs = getEffectiveRightsControl.getAttributes();
}
//If an ACI evaluated because of an Targetattr="*", then the
//AciHandler.maySend method signaled this via adding this attachment
//string.
String allUserAttrs=
(String)operation.getAttachment(ALL_USER_ATTRS_MATCHED);
if(allUserAttrs != null)
evalAllAttributes |= ACI_USER_ATTR_STAR_MATCHED;
//If an ACI evaluated because of an Targetattr="+", then the
//AciHandler.maySend method signaled this via adding this attachment
//string.
String allOpAttrs=(String)operation.getAttachment(ALL_OP_ATTRS_MATCHED);
if(allOpAttrs != null)
evalAllAttributes |= ACI_OP_ATTR_PLUS_MATCHED;
}
//Reference the current authorization entry, so it can be put back
//if an access proxy check was performed.
this.saveAuthorizationEntry=this.authorizationEntry;
this.rightsMask = rights;
}
/**
* This constructor is used by the generic access control check.
*
* @param operation The operation to use in the access evaluation.
* @param e The entry to check access for.
* @param authInfo The authentication information to use in the evaluation.
* @param rights The rights to check access of.
*/
protected AciContainer(Operation operation, Entry e,
AuthenticationInfo authInfo,
int rights) {
this.resourceEntry=e;
this.operation=operation;
this.clientConnection=operation.getClientConnection();
this.authInfo = authInfo;
this.authorizationEntry = authInfo.getAuthorizationEntry();
this.saveAuthorizationEntry=this.authorizationEntry;
this.rightsMask = rights;
}
/**
* Returns true if an entry has already been processed by an access proxy
* check.
*
* @return True if an entry has already been processed by an access proxy
* check.
*/
public boolean hasSeenEntry() {
return this.seenEntry;
}
/**
* Set to true if an entry has already been processsed by an access proxy
* check.
*
* @param val The value to set the seenEntry boolean to.
*/
public void setSeenEntry(boolean val) {
this.seenEntry=val;
}
/**
* {@inheritDoc}
*/
public boolean isProxiedAuthorization() {
return this.proxiedAuthorization;
}
/**
* {@inheritDoc}
*/
public boolean isGetEffectiveRightsEval() {
return this.isGetEffectiveRightsEval;
}
/**
* The container is going to be used in a geteffectiverights evaluation, set
* the flag isGetEffectiveRightsEval to true.
*/
public void setGetEffectiveRightsEval() {
this.isGetEffectiveRightsEval=true;
}
/**
* Return true if the container is being used in a geteffectiverights
* evaluation.
*
* @return True if the container is being used in a geteffectiverights
* evaluation.
*/
public boolean hasGetEffectiveRightsControl() {
return this.hasGetEffectiveRightsControl;
}
/**
* Use the DN from the geteffectiverights control's authzId as the
* client DN, rather than the authorization entry's DN.
*
* @param v The valued to set the useAuthzid to.
*/
public void useAuthzid(boolean v) {
this.useAuthzid=v;
}
/**
* Return the list of additional attributes specified in the
* geteffectiveritghts control.
*
* @return The list of attributes to return rights information about in the
* entry.
*/
public List<AttributeType> getSpecificAttributes() {
return this.specificAttrs;
}
/**
* {@inheritDoc}
*/
public void addTargAttrFiltersMatchAci(Aci aci) {
this.targAttrFilterAcis.put(aci, aci);
}
/**
* {@inheritDoc}
*/
public boolean hasTargAttrFiltersMatchAci(Aci aci) {
return this.targAttrFilterAcis.containsKey(aci);
}
/**
* {@inheritDoc}
*/
public boolean isTargAttrFilterMatchAciEmpty() {
return this.targAttrFilterAcis.isEmpty();
}
/**
* Reset the values used by the geteffectiverights evaluation to
* original values. The geteffectiverights evaluation uses the same container
* repeatedly for different rights evaluations (read, write, proxy,...) and
* this method resets variables that are specific to a single evaluation.
*/
public void resetEffectiveRightsParams() {
this.targAttrFilterAcis.clear();
this.decidingAci=null;
this.evalReason=null;
this.targAttrFiltersMatch=false;
this.summaryString=null;
this.targAttrMatch=0;
}
/**
* {@inheritDoc}
*/
public void setTargAttrFiltersAciName(String name) {
this.targAttrFiltersAciName=name;
}
/**
* {@inheritDoc}
*/
public String getTargAttrFiltersAciName() {
return this.targAttrFiltersAciName;
}
/**
* {@inheritDoc}
*/
public void setTargAttrFiltersMatchOp(int flag) {
this.targAttrMatch |= flag;
}
/**
* {@inheritDoc}
*/
public boolean hasTargAttrFiltersMatchOp(int flag) {
return (this.targAttrMatch & flag) != 0;
}
/**
* {@inheritDoc}
*/
public void setDecidingAci(Aci aci) {
this.decidingAci=aci;
}
/**
* {@inheritDoc}
*/
public String getDecidingAciName() {
if(this.decidingAci != null)
return this.decidingAci.getName();
else return null;
}
/**
* {@inheritDoc}
*/
public void setEvalReason(EnumEvalReason reason) {
this.evalReason=reason;
}
/**
* {@inheritDoc}
*/
public EnumEvalReason getEvalReason() {
return this.evalReason;
}
/**
* {@inheritDoc}
*/
public void setEvalSummary(String summary) {
this.summaryString=summary;
}
/**
* {@inheritDoc}
*/
public String getEvalSummary() {
return this.summaryString;
}
/**
* Returns true if the geteffectiverights control's authZid DN is equal to the
* authoritzation entry's DN.
*
* @return True if the authZid is equal to the authorization entry's DN.
*/
public boolean isAuthzidAuthorizationDN() {
return this.authzid.equals(this.authorizationEntry.getDN());
}
/**
* If the specified value is true, then the original authorization entry,
* which is the entry before the switch performed by the proxied
* authorization control processing should be set to the current
* authorization entry. If the specified value is false then the proxied
* authorization entry is switched back using the saved copy.
* @param val The value used to select the authorization entry to use.
*/
public void useOrigAuthorizationEntry(boolean val) {
if(val)
authorizationEntry=origAuthorizationEntry;
else
authorizationEntry=saveAuthorizationEntry;
}
/**
* {@inheritDoc}
*/
public void setDenyList(LinkedList<Aci> denys) {
denyList=denys;
}
/**
* {@inheritDoc}
*/
public void setAllowList(LinkedList<Aci> allows) {
allowList=allows;
}
/**
* {@inheritDoc}
*/
public AttributeType getCurrentAttributeType() {
return attributeType;
}
/**
* {@inheritDoc}
*/
public AttributeValue getCurrentAttributeValue() {
return attributeValue;
}
/**
* {@inheritDoc}
*/
public void setCurrentAttributeType(AttributeType type) {
attributeType=type;
}
/**
* {@inheritDoc}
*/
public void setCurrentAttributeValue(AttributeValue value) {
attributeValue=value;
}
/**
* {@inheritDoc}
*/
public boolean isFirstAttribute() {
return isFirst;
}
/**
* {@inheritDoc}
*/
public void setIsFirstAttribute(boolean val) {
isFirst=val;
}
/**
* {@inheritDoc}
*/
public boolean hasEntryTestRule() {
return isEntryTestRule;
}
/**
* {@inheritDoc}
*/
public void setEntryTestRule(boolean val) {
isEntryTestRule=val;
}
/**
* {@inheritDoc}
*/
public Entry getResourceEntry() {
return resourceEntry;
}
/**
* {@inheritDoc}
*/
public Entry getClientEntry() {
return this.authorizationEntry;
}
/**
* {@inheritDoc}
*/
public LinkedList<Aci> getDenyList() {
return denyList;
}
/**
* {@inheritDoc}
*/
public LinkedList<Aci> getAllowList() {
return allowList;
}
/**
* {@inheritDoc}
*/
public boolean isDenyEval() {
return isDenyEval;
}
/**
* {@inheritDoc}
*/
public boolean isAnonymousUser() {
return !authInfo.isAuthenticated();
}
/**
* {@inheritDoc}
*/
public void setDenyEval(boolean val) {
isDenyEval = val;
}
/**
* {@inheritDoc}
*/
public DN getClientDN() {
if(this.useAuthzid)
return this.authzid;
else
if (this.authorizationEntry == null)
return DN.nullDN();
else
return this.authorizationEntry.getDN();
}
/**
* {@inheritDoc}
*/
public DN getResourceDN() {
return resourceEntry.getDN();
}
/**
* {@inheritDoc}
*/
public boolean hasRights(int rights) {
return (this.rightsMask & rights) != 0;
}
/**
* {@inheritDoc}
*/
public int getRights() {
return this.rightsMask;
}
/**
* {@inheritDoc}
*/
public void setRights(int rights) {
this.rightsMask=rights;
}
/**
* {@inheritDoc}
*/
public String getHostName() {
return clientConnection.getRemoteAddress().getCanonicalHostName();
}
/**
* {@inheritDoc}
*/
public InetAddress getRemoteAddress() {
return clientConnection.getRemoteAddress();
}
/**
* {@inheritDoc}
*/
public boolean isAddOperation() {
return isAddOp;
}
/**
* {@inheritDoc}
*/
public void setTargAttrFiltersMatch(boolean v) {
this.targAttrFiltersMatch=v;
}
/**
* {@inheritDoc}
*/
public boolean getTargAttrFiltersMatch() {
return targAttrFiltersMatch;
}
/**
* {@inheritDoc}
*/
public String getControlOID() {
return controlOID;
}
/**
* {@inheritDoc}
*/
public String getExtOpOID() {
return extOpOID;
}
/**
* Set the the controlOID value to the specified oid string.
*
* @param oid The control oid string.
*/
protected void setControlOID(String oid) {
this.controlOID=oid;
}
/**
* Set the extended operation OID value to the specified oid string.
*
* @param oid The extended operation oid string.
*/
protected void setExtOpOID(String oid) {
this.extOpOID=oid;
}
/**
* {@inheritDoc}
*/
public EnumEvalResult hasAuthenticationMethod(EnumAuthMethod authMethod,
String saslMech) {
EnumEvalResult matched=EnumEvalResult.FALSE;
if(authMethod==EnumAuthMethod.AUTHMETHOD_NONE) {
/**
* None actually means any, in that we don't care what method was used.
* This doesn't seem very intuitive or useful, but that's the way it is.
*/
matched = EnumEvalResult.TRUE;
} else {
/*
* Some kind of authentication is required.
*/
if(authInfo.isAuthenticated()) {
if(authMethod==EnumAuthMethod.AUTHMETHOD_SIMPLE) {
if(authInfo.hasAuthenticationType(AuthenticationType.SIMPLE)) {
matched = EnumEvalResult.TRUE;
}
} else if(authMethod == EnumAuthMethod.AUTHMETHOD_SSL) {
/*
* This means authentication using a certificate over TLS.
*
* We check the following:
* - SASL EXTERNAL has been used, and
* - TLS is the security provider, and
* - The client provided a certificate.
*/
if (authInfo.hasAuthenticationType(AuthenticationType.SASL) &&
authInfo.hasSASLMechanism(saslMech)) {
if(clientConnection instanceof LDAPClientConnection) {
LDAPClientConnection lc =
(LDAPClientConnection) clientConnection;
Certificate[] certChain = lc.getClientCertificateChain();
if(certChain.length != 0)
matched = EnumEvalResult.TRUE;
}
}
} else {
// A particular SASL mechanism.
if (authInfo.hasAuthenticationType(AuthenticationType.SASL) &&
authInfo.hasSASLMechanism(saslMech)) {
matched = EnumEvalResult.TRUE;
}
}
}
}
return matched;
}
/**
* {@inheritDoc}
*/
public boolean isMemberOf(Group<?> group) {
boolean ret;
try {
if(useAuthzid) {
ret = group.isMember(this.authzid);
} else {
Entry e = getClientEntry();
if(e != null) {
ret=group.isMember(e);
} else {
ret=group.isMember(getClientDN());
}
}
} catch (DirectoryException ex) {
ret=false;
}
return ret;
}
/**
* {@inheritDoc}
*/
public String rightToString() {
if(hasRights(ACI_SEARCH))
return "search";
else if(hasRights(ACI_COMPARE))
return "compare";
else if(hasRights(ACI_READ))
return "read";
else if(hasRights(ACI_DELETE))
return "delete";
else if(hasRights(ACI_ADD))
return "add";
else if(hasRights(ACI_WRITE))
return "write";
else if(hasRights(ACI_PROXY))
return "proxy";
else if(hasRights(ACI_IMPORT))
return "import";
else if(hasRights(ACI_EXPORT))
return "export";
else if(hasRights(ACI_WRITE) &&
hasRights(ACI_SELF))
return "selfwrite";
return null;
}
/**
* {@inheritDoc}
*/
public void setEvalUserAttributes(int v) {
if(operation instanceof SearchOperation && (rightsMask == ACI_READ)) {
if(v == ACI_FOUND_USER_ATTR_RULE) {
evalAllAttributes |= ACI_FOUND_USER_ATTR_RULE;
evalAllAttributes &= ~ACI_USER_ATTR_STAR_MATCHED;
} else
evalAllAttributes |= ACI_USER_ATTR_STAR_MATCHED;
}
}
/**
* {@inheritDoc}
*/
public void setEvalOpAttributes(int v) {
if(operation instanceof SearchOperation && (rightsMask == ACI_READ)) {
if(v == ACI_FOUND_OP_ATTR_RULE) {
evalAllAttributes |= ACI_FOUND_OP_ATTR_RULE;
evalAllAttributes &= ~ACI_OP_ATTR_PLUS_MATCHED;
} else
evalAllAttributes |= ACI_OP_ATTR_PLUS_MATCHED;
}
}
/**
* {@inheritDoc}
*/
public boolean hasEvalUserAttributes() {
return (evalAllAttributes & ACI_FOUND_USER_ATTR_RULE) ==
ACI_FOUND_USER_ATTR_RULE;
}
/**
* {@inheritDoc}
*/
public boolean hasEvalOpAttributes() {
return (evalAllAttributes & ACI_FOUND_OP_ATTR_RULE) ==
ACI_FOUND_OP_ATTR_RULE;
}
/**
* Return true if the evaluating ACI contained a targetattr all
* user attributes rule match.
*
* @return True if the above condition was seen.
**/
public boolean hasAllUserAttributes() {
return (evalAllAttributes & ACI_USER_ATTR_STAR_MATCHED) ==
ACI_USER_ATTR_STAR_MATCHED;
}
/**
* Return true if the evaluating ACI contained a targetattr all
* operational attributes rule match.
*
* @return True if the above condition was seen.
**/
public boolean hasAllOpAttributes() {
return (evalAllAttributes & ACI_OP_ATTR_PLUS_MATCHED) ==
ACI_OP_ATTR_PLUS_MATCHED;
}
/**
* {@inheritDoc}
*/
public void clearEvalAttributes(int v) {
if(v == 0)
evalAllAttributes=0;
else
evalAllAttributes &= ~v;
}
/**
* {@inheritDoc}
*/
public int getCurrentSSF() {
return clientConnection.getSSF();
}
}