001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2008 Sun Microsystems, Inc. 015 * Portions Copyright 2013-2016 ForgeRock AS. 016 */ 017package org.opends.server.authorization.dseecompat; 018 019import java.util.Iterator; 020import java.util.LinkedList; 021import java.util.List; 022 023import org.forgerock.i18n.LocalizedIllegalArgumentException; 024import org.forgerock.opendj.ldap.ByteString; 025import org.forgerock.opendj.ldap.DN; 026import org.forgerock.opendj.ldap.SearchScope; 027import org.forgerock.opendj.ldap.schema.AttributeType; 028import org.opends.server.core.DirectoryServer; 029import org.opends.server.types.Attribute; 030import org.opends.server.types.DirectoryException; 031import org.opends.server.types.Entry; 032import org.opends.server.types.LDAPURL; 033import org.opends.server.types.SearchFilter; 034 035import static org.opends.messages.AccessControlMessages.*; 036 037/** This class represents the userdn keyword in a bind rule. */ 038public class UserDN implements KeywordBindRule { 039 /** A dummy URL for invalid URLs such as: all, parent, anyone, self. */ 040 private static final String URL_STR = "ldap:///"; 041 042 /** This list holds a list of objects representing a EnumUserDNType URL mapping. */ 043 private final List<UserDNTypeURL> urlList; 044 /** Enumeration of the userdn operation type. */ 045 private final EnumBindRuleType type; 046 047 /** 048 * Constructor that creates the userdn class. It also sets up an attribute 049 * type ("userdn") needed for wild-card matching. 050 * @param type The type of operation. 051 * @param urlList A list of enumerations containing the URL type and URL 052 * object that can be retrieved at evaluation time. 053 */ 054 private UserDN(EnumBindRuleType type, List<UserDNTypeURL> urlList) { 055 this.type=type; 056 this.urlList=urlList; 057 } 058 059 /** 060 * Decodes an expression string representing a userdn bind rule. 061 * @param expression The string representation of the userdn bind rule 062 * expression. 063 * @param type An enumeration of the type of the bind rule. 064 * @return A KeywordBindRule class that represents the bind rule. 065 * @throws AciException If the expression failed to LDAP URL decode. 066 */ 067 public static KeywordBindRule decode(String expression, 068 EnumBindRuleType type) throws AciException { 069 String[] vals=expression.split("[|][|]"); 070 List<UserDNTypeURL> urlList = new LinkedList<>(); 071 for (String val : vals) 072 { 073 StringBuilder value = new StringBuilder(val.trim()); 074 /* 075 * TODO Evaluate using a wild-card in the dn portion of LDAP url. 076 * The current implementation (DS6) does not treat a "*" 077 * as a wild-card. 078 * 079 * Is it allowed to have a full LDAP URL (i.e., including a base, 080 * scope, and filter) in which the base DN contains asterisks to 081 * make it a wildcard? If so, then I don't think that the current 082 * implementation handles that correctly. It will probably fail 083 * when attempting to create the LDAP URL because the base DN isn't a 084 * valid DN. 085 */ 086 EnumUserDNType userDNType = UserDN.getType(value); 087 LDAPURL url; 088 try { 089 url=LDAPURL.decode(value.toString(), true); 090 } catch (LocalizedIllegalArgumentException | DirectoryException e) { 091 throw new AciException(WARN_ACI_SYNTAX_INVALID_USERDN_URL.get(e.getMessageObject())); 092 } 093 urlList.add(new UserDNTypeURL(userDNType, url)); 094 } 095 return new UserDN(type, urlList); 096 } 097 098 /** 099 * This method determines the type of the DN (suffix in URL terms) 100 * part of a URL, by examining the full URL itself for known strings 101 * such as (corresponding type shown in parenthesis) 102 * 103 * "ldap:///anyone" (EnumUserDNType.ANYONE) 104 * "ldap:///parent" (EnumUserDNType.PARENT) 105 * "ldap:///all" (EnumUserDNType.ALL) 106 * "ldap:///self" (EnumUserDNType.SELF) 107 * 108 * If one of the four above are found, the URL is replaced with a dummy 109 * pattern "ldap:///". This is done because the above four are invalid 110 * URLs; but the syntax is valid for an userdn keyword expression. The 111 * dummy URLs are never used. 112 * 113 * If none of the above are found, it determine if the URL DN is a 114 * substring pattern, such as: 115 * 116 * "ldap:///uid=*, dc=example, dc=com" (EnumUserDNType.PATTERN) 117 * 118 * If none of the above are determined, it checks if the URL 119 * is a complete URL with scope and filter defined: 120 * 121 * "ldap:///uid=test,dc=example,dc=com??sub?(cn=j*)" (EnumUserDNType.URL) 122 * 123 * If none of these those types can be identified, it defaults to 124 * EnumUserDNType.DN. 125 * 126 * @param bldr A string representation of the URL that can be modified. 127 * @return The user DN type of the URL. 128 */ 129 private static EnumUserDNType getType(StringBuilder bldr) { 130 String str=bldr.toString(); 131 if (str.contains("?")) { 132 return EnumUserDNType.URL; 133 } else if(str.equalsIgnoreCase("ldap:///self")) { 134 bldr.replace(0, bldr.length(), URL_STR); 135 return EnumUserDNType.SELF; 136 } else if(str.equalsIgnoreCase("ldap:///anyone")) { 137 bldr.replace(0, bldr.length(), URL_STR); 138 return EnumUserDNType.ANYONE; 139 } else if(str.equalsIgnoreCase("ldap:///parent")) { 140 bldr.replace(0, bldr.length(), URL_STR); 141 return EnumUserDNType.PARENT; 142 } else if(str.equalsIgnoreCase("ldap:///all")) { 143 bldr.replace(0, bldr.length(), URL_STR); 144 return EnumUserDNType.ALL; 145 } else if (str.contains("*")) { 146 return EnumUserDNType.DNPATTERN; 147 } else { 148 return EnumUserDNType.DN; 149 } 150 } 151 152 /** 153 * Performs the evaluation of a userdn bind rule based on the 154 * evaluation context passed to it. The evaluation stops when there 155 * are no more UserDNTypeURLs to evaluate or if an UserDNTypeURL 156 * evaluates to true. 157 * @param evalCtx The evaluation context to evaluate with. 158 * @return An evaluation result enumeration containing the result 159 * of the evaluation. 160 */ 161 @Override 162 public EnumEvalResult evaluate(AciEvalContext evalCtx) { 163 EnumEvalResult matched = EnumEvalResult.FALSE; 164 boolean undefined=false; 165 166 boolean isAnonUser=evalCtx.isAnonymousUser(); 167 Iterator<UserDNTypeURL> it=urlList.iterator(); 168 for(; it.hasNext() && matched != EnumEvalResult.TRUE && 169 matched != EnumEvalResult.ERR;) { 170 UserDNTypeURL dnTypeURL=it.next(); 171 //Handle anonymous checks here 172 if(isAnonUser) { 173 if(dnTypeURL.getUserDNType() == EnumUserDNType.ANYONE) 174 { 175 matched = EnumEvalResult.TRUE; 176 } 177 } 178 else 179 { 180 matched=evalNonAnonymous(evalCtx, dnTypeURL); 181 } 182 } 183 return matched.getRet(type, undefined); 184 } 185 186 /** 187 * Performs an evaluation of a single UserDNTypeURL of a userdn bind 188 * rule using the evaluation context provided. This method is called 189 * for the non-anonymous user case. 190 * @param evalCtx The evaluation context to evaluate with. 191 * @param dnTypeURL The URL dn type mapping to evaluate. 192 * @return An evaluation result enumeration containing the result 193 * of the evaluation. 194 */ 195 private EnumEvalResult evalNonAnonymous(AciEvalContext evalCtx, 196 UserDNTypeURL dnTypeURL) { 197 return evalNonAnonymous0(evalCtx, dnTypeURL) ? EnumEvalResult.TRUE : EnumEvalResult.FALSE; 198 } 199 200 private boolean evalNonAnonymous0(AciEvalContext evalCtx, 201 UserDNTypeURL dnTypeURL) { 202 DN clientDN=evalCtx.getClientDN(); 203 DN resDN=evalCtx.getResourceDN(); 204 EnumUserDNType type=dnTypeURL.getUserDNType(); 205 LDAPURL url=dnTypeURL.getURL(); 206 switch (type) { 207 case URL: 208 return evalURL0(evalCtx, url); 209 case ANYONE: 210 case ALL: 211 return true; 212 case SELF: 213 return clientDN.equals(resDN); 214 case PARENT: 215 DN parentDN = resDN.parent(); 216 return parentDN != null && parentDN.equals(clientDN); 217 case DNPATTERN: 218 return evalDNPattern(evalCtx, url); 219 case DN: 220 return evalDN(clientDN, url); 221 default: 222 return false; 223 } 224 } 225 226 private boolean evalDN(DN clientDN, LDAPURL url) 227 { 228 try 229 { 230 DN dn = url.getBaseDN(); 231 if (clientDN.equals(dn)) 232 { 233 return true; 234 } 235 236 // This code handles the case where a root dn entry does 237 // not have bypass-acl privilege and the ACI bind rule 238 // userdn DN possible is an alternate root DN. 239 DN actualDN = DirectoryServer.getActualRootBindDN(dn); 240 DN clientActualDN = DirectoryServer.getActualRootBindDN(clientDN); 241 if (actualDN != null) 242 { 243 dn = actualDN; 244 } 245 if (clientActualDN != null) 246 { 247 clientDN = clientActualDN; 248 } 249 return clientDN.equals(dn); 250 } catch (DirectoryException ex) { 251 //TODO add message 252 return false; 253 } 254 } 255 256 /** 257 * This method evaluates a DN pattern userdn expression. 258 * @param evalCtx The evaluation context to use. 259 * @param url The LDAP URL containing the pattern. 260 * @return An enumeration evaluation result. 261 */ 262 private boolean evalDNPattern(AciEvalContext evalCtx, LDAPURL url) { 263 PatternDN pattern; 264 try { 265 pattern = PatternDN.decode(url.getRawBaseDN()); 266 } catch (DirectoryException ex) { 267 return false; 268 } 269 270 return pattern.matchesDN(evalCtx.getClientDN()); 271 } 272 273 274 /** 275 * This method evaluates an URL userdn expression. Something like: 276 * ldap:///suffix??sub?(filter). It also searches for the client DN 277 * entry and saves it in the evaluation context for repeat evaluations 278 * that might come later in processing. 279 * 280 * @param evalCtx The evaluation context to use. 281 * @param url URL containing the URL to use in the evaluation. 282 * @return An enumeration of the evaluation result. 283 */ 284 public static EnumEvalResult evalURL(AciEvalContext evalCtx, LDAPURL url) { 285 return evalURL0(evalCtx, url) ? EnumEvalResult.TRUE : EnumEvalResult.FALSE; 286 } 287 288 private static boolean evalURL0(AciEvalContext evalCtx, LDAPURL url) { 289 DN urlDN; 290 SearchFilter filter; 291 try { 292 urlDN=url.getBaseDN(); 293 filter=url.getFilter(); 294 } catch (DirectoryException ex) { 295 return false; 296 } 297 SearchScope scope=url.getScope(); 298 if(scope == SearchScope.WHOLE_SUBTREE) { 299 if(!evalCtx.getClientDN().isSubordinateOrEqualTo(urlDN)) 300 { 301 return false; 302 } 303 } else if(scope == SearchScope.SINGLE_LEVEL) { 304 DN parent=evalCtx.getClientDN().parent(); 305 if(parent != null && !parent.equals(urlDN)) 306 { 307 return false; 308 } 309 } else if(scope == SearchScope.SUBORDINATES) { 310 DN userDN = evalCtx.getClientDN(); 311 if (userDN.size() <= urlDN.size() || 312 !userDN.isSubordinateOrEqualTo(urlDN)) { 313 return false; 314 } 315 } else { 316 if(!evalCtx.getClientDN().equals(urlDN)) 317 { 318 return false; 319 } 320 } 321 try { 322 return (filter.matchesEntry(evalCtx.getClientEntry())); 323 } catch (DirectoryException ex) { 324 return false; 325 } 326 } 327 328 /* 329 * TODO Evaluate making this method more efficient. 330 * 331 * The evalDNEntryAttr method isn't as efficient as it could be. 332 * It would probably be faster to to convert the clientDN to a ByteString 333 * and see if the entry has that value than to decode each value as a DN 334 * and see if it matches the clientDN. 335 */ 336 /** 337 * This method searches an entry for an attribute value that is 338 * treated as a DN. That DN is then compared against the client 339 * DN. 340 * @param e The entry to get the attribute type from. 341 * @param clientDN The client authorization DN to check for. 342 * @param attrType The attribute type from the bind rule. 343 * @return An enumeration with the result. 344 */ 345 public static boolean evaluate(Entry e, DN clientDN, 346 AttributeType attrType) { 347 List<Attribute> attrs = e.getAttribute(attrType); 348 for(ByteString v : attrs.get(0)) { 349 try { 350 DN dn = DN.valueOf(v.toString()); 351 if(dn.equals(clientDN)) { 352 return true; 353 } 354 } catch (LocalizedIllegalArgumentException ignored) { 355 break; 356 } 357 } 358 return false; 359 } 360 361 @Override 362 public String toString() { 363 final StringBuilder sb = new StringBuilder(); 364 toString(sb); 365 return sb.toString(); 366 } 367 368 @Override 369 public final void toString(StringBuilder buffer) { 370 buffer.append("userdn"); 371 buffer.append(this.type.getType()); 372 for (UserDNTypeURL url : this.urlList) { 373 buffer.append("\""); 374 buffer.append(URL_STR); 375 buffer.append(url.getUserDNType().toString().toLowerCase()); 376 buffer.append("\""); 377 } 378 } 379}