AciContainer.java revision 88f16d892d54fd8c3e190cc1f6363638b11ae1a3
/*
* 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
* or http://forgerock.org/license/CDDLv1.0.html.
* 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 Sun Microsystems, Inc.
* Portions Copyright 2011-2015 ForgeRock AS
*/
package org.opends.server.authorization.dseecompat;
import static org.opends.server.authorization.dseecompat.Aci.*;
import static org.opends.server.authorization.dseecompat.AciHandler.*;
import static org.opends.server.util.ServerConstants.*;
import java.net.InetAddress;
import java.security.cert.Certificate;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import org.forgerock.opendj.ldap.ByteString;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.Group;
import org.opends.server.controls.GetEffectiveRightsRequestControl;
import org.opends.server.core.AddOperation;
import org.opends.server.core.SearchOperation;
import org.opends.server.protocols.ldap.LDAPClientConnection;
import org.opends.server.types.*;
/**
* 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 List<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 ByteString attributeValue;
/**
* True if this is the first attribute type in the resource entry being
* evaluated.
*/
private boolean isFirst;
/**
* 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;
/**
* 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;
/**
* 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;
/**
* True if proxied authorization is being used.
*/
private boolean proxiedAuthorization;
/**
* 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;
/**
* True if geteffectiverights evaluation is in progress.
*/
private boolean isGetEffectiveRightsEval;
/**
* True if the operation has a geteffectiverights control.
*/
private boolean hasGetEffectiveRightsControl;
/**
* The geteffectiverights authzID in DN format.
*/
private DN authzid;
/**
* True if the authZid should be used as the client DN, only used in
* geteffectiverights evaluation.
*/
private boolean useAuthzid;
/**
* The list of specific attributes to get rights for, in addition to
* any attributes requested in the search.
*/
private List<AttributeType> specificAttrs;
/**
* 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;
/**
* 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;
/**
* The ACI that decided the last evaluation. Used in geteffectiverights
* loginfo processing.
*/
private Aci decidingAci;
/**
* The reason the last evaluation decision was made. Used both
* in geteffectiverights loginfo processing and attributeLevel write
* evaluation.
*/
private EnumEvalReason evalReason;
/**
* A summary string holding the last evaluation information in textual
* format. Used in geteffectiverights loginfo processing.
*/
private String summaryString;
/**
* Flag used to determine if ACI all attributes target matched.
*/
private int evalAllAttributes;
/**
* 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();
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);
this.proxiedAuthorization = origAuthorizationEntry != null;
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 processed by an access proxy
* check.
*
* @param val The value to set the seenEntry boolean to.
*/
public void setSeenEntry(boolean val) {
this.seenEntry=val;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isProxiedAuthorization() {
return this.proxiedAuthorization;
}
/**
* {@inheritDoc}
*/
@Override
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
* geteffectiverights control.
*
* @return The list of attributes to return rights information about in the
* entry.
*/
public List<AttributeType> getSpecificAttributes() {
return this.specificAttrs;
}
/**
* {@inheritDoc}
*/
@Override
public void addTargAttrFiltersMatchAci(Aci aci) {
this.targAttrFilterAcis.put(aci, aci);
}
/**
* {@inheritDoc}
*/
@Override
public boolean hasTargAttrFiltersMatchAci(Aci aci) {
return this.targAttrFilterAcis.containsKey(aci);
}
/**
* {@inheritDoc}
*/
@Override
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}
*/
@Override
public void setTargAttrFiltersAciName(String name) {
this.targAttrFiltersAciName=name;
}
/**
* {@inheritDoc}
*/
@Override
public String getTargAttrFiltersAciName() {
return this.targAttrFiltersAciName;
}
/**
* {@inheritDoc}
*/
@Override
public void setTargAttrFiltersMatchOp(int flag) {
this.targAttrMatch |= flag;
}
/**
* {@inheritDoc}
*/
@Override
public boolean hasTargAttrFiltersMatchOp(int flag) {
return (this.targAttrMatch & flag) != 0;
}
/**
* {@inheritDoc}
*/
@Override
public String getDecidingAciName() {
if(this.decidingAci != null)
return this.decidingAci.getName();
else return null;
}
/** {@inheritDoc} */
@Override
public void setEvaluationResult(EnumEvalReason reason, Aci decidingAci)
{
this.evalReason = reason;
this.decidingAci = decidingAci;
}
/**
* {@inheritDoc}
*/
@Override
public EnumEvalReason getEvalReason() {
return this.evalReason;
}
/**
* {@inheritDoc}
*/
@Override
public void setEvalSummary(String summary) {
this.summaryString=summary;
}
/**
* {@inheritDoc}
*/
@Override
public String getEvalSummary() {
return this.summaryString;
}
/**
* Returns true if the geteffectiverights control's authZid DN is equal to the
* authorization 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.getName());
}
/**
* 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}
*/
@Override
public void setDenyList(List<Aci> denys) {
denyList=denys;
}
/**
* {@inheritDoc}
*/
@Override
public void setAllowList(List<Aci> allows) {
allowList=allows;
}
/**
* {@inheritDoc}
*/
@Override
public AttributeType getCurrentAttributeType() {
return attributeType;
}
/**
* {@inheritDoc}
*/
@Override
public ByteString getCurrentAttributeValue() {
return attributeValue;
}
/**
* {@inheritDoc}
*/
@Override
public void setCurrentAttributeType(AttributeType type) {
attributeType=type;
}
/**
* {@inheritDoc}
*/
@Override
public void setCurrentAttributeValue(ByteString value) {
attributeValue=value;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isFirstAttribute() {
return isFirst;
}
/**
* {@inheritDoc}
*/
@Override
public void setIsFirstAttribute(boolean val) {
isFirst=val;
}
/**
* {@inheritDoc}
*/
@Override
public boolean hasEntryTestRule() {
return isEntryTestRule;
}
/**
* {@inheritDoc}
*/
@Override
public void setEntryTestRule(boolean val) {
isEntryTestRule=val;
}
/**
* {@inheritDoc}
*/
@Override
public Entry getResourceEntry() {
return resourceEntry;
}
/**
* {@inheritDoc}
*/
@Override
public Entry getClientEntry() {
return this.authorizationEntry;
}
/**
* {@inheritDoc}
*/
@Override
public List<Aci> getDenyList() {
return denyList;
}
/**
* {@inheritDoc}
*/
@Override
public List<Aci> getAllowList() {
return allowList;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isDenyEval() {
return EnumEvalReason.NO_ALLOW_ACIS.equals(evalReason)
|| EnumEvalReason.EVALUATED_DENY_ACI.equals(evalReason);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isAnonymousUser() {
return !authInfo.isAuthenticated();
}
/**
* {@inheritDoc}
*/
@Override
public DN getClientDN() {
if(this.useAuthzid)
return this.authzid;
else if (this.authorizationEntry != null)
return this.authorizationEntry.getName();
return DN.rootDN();
}
/**
* {@inheritDoc}
*/
@Override
public DN getResourceDN() {
return resourceEntry.getName();
}
/**
* {@inheritDoc}
* <p>
* JNR: I find the implementation in this method dubious.
*
* @see EnumRight#hasRights(int, int)
*/
@Override
public boolean hasRights(int rights) {
return (this.rightsMask & rights) != 0;
}
/**
* {@inheritDoc}
*/
@Override
public int getRights() {
return this.rightsMask;
}
/**
* {@inheritDoc}
*/
@Override
public void setRights(int rights) {
this.rightsMask=rights;
}
/**
* {@inheritDoc}
*/
@Override
public String getHostName() {
return clientConnection.getRemoteAddress().getCanonicalHostName();
}
/**
* {@inheritDoc}
*/
@Override
public InetAddress getRemoteAddress() {
return clientConnection.getRemoteAddress();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isAddOperation() {
return operation instanceof AddOperation;
}
/**
* {@inheritDoc}
*/
@Override
public void setTargAttrFiltersMatch(boolean v) {
this.targAttrFiltersMatch=v;
}
/**
* {@inheritDoc}
*/
@Override
public boolean getTargAttrFiltersMatch() {
return targAttrFiltersMatch;
}
/**
* {@inheritDoc}
*/
@Override
public String getControlOID() {
return controlOID;
}
/**
* {@inheritDoc}
*/
@Override
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}
*/
@Override
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}
*/
@Override
public boolean isMemberOf(Group<?> group) {
try {
if(useAuthzid) {
return group.isMember(this.authzid);
}
Entry e = getClientEntry();
if (e != null) {
return group.isMember(e);
}
return group.isMember(getClientDN());
} catch (DirectoryException ex) {
return false;
}
}
/**
* {@inheritDoc}
* <p>
* JNR: I find the implementation in this method dubious.
*
* @see EnumRight#getEnumRight(int)
*/
@Override
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}
*/
@Override
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}
*/
@Override
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}
*/
@Override
public boolean hasEvalUserAttributes() {
return hasAttribute(ACI_FOUND_USER_ATTR_RULE);
}
/**
* {@inheritDoc}
*/
@Override
public boolean hasEvalOpAttributes() {
return hasAttribute(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 hasAttribute(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 hasAttribute(ACI_OP_ATTR_PLUS_MATCHED);
}
private boolean hasAttribute(int aciAttribute)
{
return (evalAllAttributes & aciAttribute) == aciAttribute;
}
/**
* {@inheritDoc}
*/
@Override
public void clearEvalAttributes(int v) {
if(v == 0)
evalAllAttributes=0;
else
evalAllAttributes &= ~v;
}
/**
* {@inheritDoc}
*/
@Override
public int getCurrentSSF() {
return clientConnection.getSSF();
}
/** {@inheritDoc} */
@Override
public String toString()
{
final StringBuilder sb = new StringBuilder();
if (attributeType != null)
{
appendSeparatorIfNeeded(sb);
sb.append("attributeType: ").append(attributeType.getNameOrOID());
if (attributeValue != null)
{
sb.append(":").append(attributeValue);
}
}
appendSeparatorIfNeeded(sb);
sb.append(size(allowList)).append(" allow ACIs");
appendSeparatorIfNeeded(sb);
sb.append(size(denyList)).append(" deny ACIs");
if (evalReason != null)
{
appendSeparatorIfNeeded(sb);
sb.append("evaluationResult: ").append(evalReason);
if (decidingAci != null)
{
sb.append(",").append(decidingAci);
}
}
return sb.toString();
}
private void appendSeparatorIfNeeded(StringBuilder sb)
{
if (sb.length() > 0)
{
sb.append(", ");
}
}
private int size(Collection<?> col)
{
if (col != null)
{
return col.size();
}
return 0;
}
}