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