UserDN.java revision 917eb33ca3ffb73a34c0f733227d8f2215f9d978
809N/A/*
2362N/A * CDDL HEADER START
809N/A *
809N/A * The contents of this file are subject to the terms of the
809N/A * Common Development and Distribution License, Version 1.0 only
809N/A * (the "License"). You may not use this file except in compliance
809N/A * with the License.
809N/A *
809N/A * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
809N/A * or http://forgerock.org/license/CDDLv1.0.html.
809N/A * See the License for the specific language governing permissions
809N/A * and limitations under the License.
809N/A *
809N/A * When distributing Covered Code, include this CDDL HEADER in each
809N/A * file and include the License file at legal-notices/CDDLv1_0.txt.
809N/A * If applicable, add the following below this CDDL HEADER, with the
809N/A * fields enclosed by brackets "[]" replaced with your own identifying
809N/A * information:
2362N/A * Portions Copyright [yyyy] [name of copyright owner]
2362N/A *
2362N/A * CDDL HEADER END
809N/A *
809N/A *
809N/A * Copyright 2008 Sun Microsystems, Inc.
809N/A * Portions Copyright 2013-2015 ForgeRock AS
809N/A */
809N/Apackage org.opends.server.authorization.dseecompat;
4014N/A
4014N/Aimport java.util.Iterator;
809N/Aimport java.util.LinkedList;
809N/Aimport java.util.List;
809N/A
809N/Aimport org.forgerock.i18n.LocalizableMessage;
809N/Aimport org.forgerock.opendj.ldap.ByteString;
809N/Aimport org.forgerock.opendj.ldap.SearchScope;
809N/Aimport org.opends.server.core.DirectoryServer;
809N/Aimport org.opends.server.types.*;
809N/A
809N/Aimport static org.opends.messages.AccessControlMessages.*;
809N/A
809N/A/**
809N/A * This class represents the userdn keyword in a bind rule.
809N/A */
809N/Apublic class UserDN implements KeywordBindRule {
809N/A
809N/A /**
809N/A * A dummy URL for invalid URLs such as: all, parent, anyone, self.
809N/A */
809N/A private static String urlStr="ldap:///";
809N/A
809N/A /**
809N/A * This list holds a list of objects representing a EnumUserDNType
809N/A * URL mapping.
809N/A */
809N/A private List<UserDNTypeURL> urlList;
809N/A
809N/A /** Enumeration of the userdn operation type. */
809N/A private EnumBindRuleType type;
809N/A
809N/A /**
809N/A * Constructor that creates the userdn class. It also sets up an attribute
809N/A * type ("userdn") needed for wild-card matching.
809N/A * @param type The type of operation.
809N/A * @param urlList A list of enumerations containing the URL type and URL
4014N/A * object that can be retrieved at evaluation time.
4014N/A */
4014N/A private UserDN(EnumBindRuleType type, List<UserDNTypeURL> urlList) {
4014N/A this.type=type;
4014N/A this.urlList=urlList;
809N/A }
809N/A
809N/A /**
809N/A * Decodes an expression string representing a userdn bind rule.
809N/A * @param expression The string representation of the userdn bind rule
809N/A * expression.
809N/A * @param type An enumeration of the type of the bind rule.
809N/A * @return A KeywordBindRule class that represents the bind rule.
809N/A * @throws AciException If the expression failed to LDAP URL decode.
809N/A */
809N/A public static KeywordBindRule decode(String expression,
809N/A EnumBindRuleType type) throws AciException {
809N/A
4014N/A String[] vals=expression.split("[|][|]");
4014N/A List<UserDNTypeURL> urlList = new LinkedList<UserDNTypeURL>();
809N/A for (String val : vals)
809N/A {
809N/A StringBuilder value = new StringBuilder(val.trim());
809N/A /*
809N/A * TODO Evaluate using a wild-card in the dn portion of LDAP url.
809N/A * The current implementation (DS6) does not treat a "*"
809N/A * as a wild-card.
809N/A *
809N/A * Is it allowed to have a full LDAP URL (i.e., including a base,
809N/A * scope, and filter) in which the base DN contains asterisks to
809N/A * make it a wildcard? If so, then I don't think that the current
* implementation handles that correctly. It will probably fail
* when attempting to create the LDAP URL because the base DN isn't a
* valid DN.
*/
EnumUserDNType userDNType = UserDN.getType(value);
LDAPURL url;
try {
url=LDAPURL.decode(value.toString(), true);
} catch (DirectoryException de) {
LocalizableMessage message = WARN_ACI_SYNTAX_INVALID_USERDN_URL.get(
de.getMessageObject());
throw new AciException(message);
}
UserDNTypeURL dnTypeURL=new UserDNTypeURL(userDNType, url);
urlList.add(dnTypeURL);
}
return new UserDN(type, urlList);
}
/**
* This method determines the type of the DN (suffix in URL terms)
* part of a URL, by examining the full URL itself for known strings
* such as (corresponding type shown in parenthesis)
*
* "ldap:///anyone" (EnumUserDNType.ANYONE)
* "ldap:///parent" (EnumUserDNType.PARENT)
* "ldap:///all" (EnumUserDNType.ALL)
* "ldap:///self" (EnumUserDNType.SELF)
*
* If one of the four above are found, the URL is replaced with a dummy
* pattern "ldap:///". This is done because the above four are invalid
* URLs; but the syntax is valid for an userdn keyword expression. The
* dummy URLs are never used.
*
* If none of the above are found, it determine if the URL DN is a
* substring pattern, such as:
*
* "ldap:///uid=*, dc=example, dc=com" (EnumUserDNType.PATTERN)
*
* If none of the above are determined, it checks if the URL
* is a complete URL with scope and filter defined:
*
* "ldap:///uid=test,dc=example,dc=com??sub?(cn=j*)" (EnumUserDNType.URL)
*
* If none of these those types can be identified, it defaults to
* EnumUserDNType.DN.
*
* @param bldr A string representation of the URL that can be modified.
* @return The user DN type of the URL.
*/
private static EnumUserDNType getType(StringBuilder bldr) {
EnumUserDNType type;
String str=bldr.toString();
if (str.contains("?")) {
type = EnumUserDNType.URL;
} else if(str.equalsIgnoreCase("ldap:///self")) {
type = EnumUserDNType.SELF;
bldr.replace(0, bldr.length(), urlStr);
} else if(str.equalsIgnoreCase("ldap:///anyone")) {
type = EnumUserDNType.ANYONE;
bldr.replace(0, bldr.length(), urlStr);
} else if(str.equalsIgnoreCase("ldap:///parent")) {
type = EnumUserDNType.PARENT;
bldr.replace(0, bldr.length(), urlStr);
} else if(str.equalsIgnoreCase("ldap:///all")) {
type = EnumUserDNType.ALL;
bldr.replace(0, bldr.length(), urlStr);
} else if (str.contains("*")) {
type = EnumUserDNType.DNPATTERN;
} else {
type = EnumUserDNType.DN;
}
return type;
}
/**
* Performs the evaluation of a userdn bind rule based on the
* evaluation context passed to it. The evaluation stops when there
* are no more UserDNTypeURLs to evaluate or if an UserDNTypeURL
* evaluates to true.
* @param evalCtx The evaluation context to evaluate with.
* @return An evaluation result enumeration containing the result
* of the evaluation.
*/
@Override
public EnumEvalResult evaluate(AciEvalContext evalCtx) {
EnumEvalResult matched = EnumEvalResult.FALSE;
boolean undefined=false;
boolean isAnonUser=evalCtx.isAnonymousUser();
Iterator<UserDNTypeURL> it=urlList.iterator();
for(; it.hasNext() && matched != EnumEvalResult.TRUE &&
matched != EnumEvalResult.ERR;) {
UserDNTypeURL dnTypeURL=it.next();
//Handle anonymous checks here
if(isAnonUser) {
if(dnTypeURL.getUserDNType() == EnumUserDNType.ANYONE)
matched = EnumEvalResult.TRUE;
} else
matched=evalNonAnonymous(evalCtx, dnTypeURL);
}
return matched.getRet(type, undefined);
}
/**
* Performs an evaluation of a single UserDNTypeURL of a userdn bind
* rule using the evaluation context provided. This method is called
* for the non-anonymous user case.
* @param evalCtx The evaluation context to evaluate with.
* @param dnTypeURL The URL dn type mapping to evaluate.
* @return An evaluation result enumeration containing the result
* of the evaluation.
*/
private EnumEvalResult evalNonAnonymous(AciEvalContext evalCtx,
UserDNTypeURL dnTypeURL) {
DN clientDN=evalCtx.getClientDN();
DN resDN=evalCtx.getResourceDN();
EnumEvalResult matched = EnumEvalResult.FALSE;
EnumUserDNType type=dnTypeURL.getUserDNType();
LDAPURL url=dnTypeURL.getURL();
switch (type) {
case URL:
{
matched = evalURL(evalCtx, url);
break;
}
case ANYONE:
{
matched = EnumEvalResult.TRUE;
break;
}
case SELF:
{
if (clientDN.equals(resDN)) matched = EnumEvalResult.TRUE;
break;
}
case PARENT:
{
DN parentDN = resDN.parent();
if ((parentDN != null) &&
(parentDN.equals(clientDN)))
matched = EnumEvalResult.TRUE;
break;
}
case ALL:
{
matched = EnumEvalResult.TRUE;
break;
}
case DNPATTERN:
{
matched = evalDNPattern(evalCtx, url);
break;
}
case DN:
{
try
{
DN dn = url.getBaseDN();
if (clientDN.equals(dn))
matched = EnumEvalResult.TRUE;
else {
//This code handles the case where a root dn entry does
//not have bypass-acl privilege and the ACI bind rule
//userdn DN possible is an alternate root DN.
DN actualDN=DirectoryServer.getActualRootBindDN(dn);
DN clientActualDN=
DirectoryServer.getActualRootBindDN(clientDN);
if(actualDN != null)
dn=actualDN;
if(clientActualDN != null)
clientDN=clientActualDN;
if(clientDN.equals(dn))
matched=EnumEvalResult.TRUE;
}
} catch (DirectoryException ex) {
//TODO add message
}
}
}
return matched;
}
/**
* This method evaluates a DN pattern userdn expression.
* @param evalCtx The evaluation context to use.
* @param url The LDAP URL containing the pattern.
* @return An enumeration evaluation result.
*/
private EnumEvalResult evalDNPattern(AciEvalContext evalCtx, LDAPURL url) {
PatternDN pattern;
try {
pattern = PatternDN.decode(url.getRawBaseDN());
} catch (DirectoryException ex) {
return EnumEvalResult.FALSE;
}
return pattern.matchesDN(evalCtx.getClientDN()) ?
EnumEvalResult.TRUE : EnumEvalResult.FALSE;
}
/**
* This method evaluates an URL userdn expression. Something like:
* ldap:///suffix??sub?(filter). It also searches for the client DN
* entry and saves it in the evaluation context for repeat evaluations
* that might come later in processing.
*
* @param evalCtx The evaluation context to use.
* @param url URL containing the URL to use in the evaluation.
* @return An enumeration of the evaluation result.
*/
public static EnumEvalResult evalURL(AciEvalContext evalCtx, LDAPURL url) {
EnumEvalResult ret=EnumEvalResult.FALSE;
DN urlDN;
SearchFilter filter;
try {
urlDN=url.getBaseDN();
filter=url.getFilter();
} catch (DirectoryException ex) {
return EnumEvalResult.FALSE;
}
SearchScope scope=url.getScope();
if(scope == SearchScope.WHOLE_SUBTREE) {
if(!evalCtx.getClientDN().isDescendantOf(urlDN))
return EnumEvalResult.FALSE;
} else if(scope == SearchScope.SINGLE_LEVEL) {
DN parent=evalCtx.getClientDN().parent();
if((parent != null) && !parent.equals(urlDN))
return EnumEvalResult.FALSE;
} else if(scope == SearchScope.SUBORDINATES) {
DN userDN = evalCtx.getClientDN();
if ((userDN.size() <= urlDN.size()) ||
!userDN.isDescendantOf(urlDN)) {
return EnumEvalResult.FALSE;
}
} else {
if(!evalCtx.getClientDN().equals(urlDN))
return EnumEvalResult.FALSE;
}
try {
if(filter.matchesEntry(evalCtx.getClientEntry()))
ret=EnumEvalResult.TRUE;
} catch (DirectoryException ex) {
return EnumEvalResult.FALSE;
}
return ret;
}
/*
* TODO Evaluate making this method more efficient.
*
* The evalDNEntryAttr method isn't as efficient as it could be.
* It would probably be faster to to convert the clientDN to a ByteString
* and see if the entry has that value than to decode each value as a DN
* and see if it matches the clientDN.
*/
/**
* This method searches an entry for an attribute value that is
* treated as a DN. That DN is then compared against the client
* DN.
* @param e The entry to get the attribute type from.
* @param clientDN The client authorization DN to check for.
* @param attrType The attribute type from the bind rule.
* @return An enumeration with the result.
*/
public static EnumEvalResult evaluate(Entry e, DN clientDN,
AttributeType attrType) {
EnumEvalResult matched= EnumEvalResult.FALSE;
List<Attribute> attrs = e.getAttribute(attrType);
for(ByteString v : attrs.get(0)) {
try {
DN dn = DN.valueOf(v.toString());
if(dn.equals(clientDN)) {
matched=EnumEvalResult.TRUE;
break;
}
} catch (DirectoryException ex) {
break;
}
}
return matched;
}
/** {@inheritDoc} */
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
toString(sb);
return sb.toString();
}
/** {@inheritDoc} */
@Override
public final void toString(StringBuilder buffer) {
buffer.append("userdn");
buffer.append(this.type.getType());
for (UserDNTypeURL url : this.urlList) {
buffer.append("\"");
buffer.append(urlStr);
buffer.append(url.getUserDNType().toString().toLowerCase());
buffer.append("\"");
}
}
}