/* * 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(); } }