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