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