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.LinkedHashSet; 033 import java.util.LinkedList; 034 import java.util.List; 035 import org.opends.server.core.DirectoryServer; 036 import org.opends.server.protocols.internal.InternalClientConnection; 037 import org.opends.server.protocols.internal.InternalSearchOperation; 038 import org.opends.server.types.*; 039 /* 040 * TODO Evaluate making this class more efficient. 041 * 042 * This class isn't as efficient as it could be. For example, the evalVAL() 043 * method should be able to use cached versions of the attribute type and 044 * filter. The evalURL() and evalDN() methods should also be able to use a 045 * cached version of the attribute type. 046 */ 047 /** 048 * This class implements the userattr bind rule keyword. 049 */ 050 public class UserAttr implements KeywordBindRule { 051 052 /** 053 * This enumeration is the various types the userattr can have after 054 * the "#" token. 055 */ 056 private enum UserAttrType { 057 USERDN, GROUPDN, ROLEDN, URL, VALUE 058 } 059 060 /* 061 * Filter used in internal search. 062 */ 063 private static SearchFilter filter; 064 065 /* 066 * Used to create an attribute type that can compare the value below in 067 * an entry returned from an internal search. 068 */ 069 private String attrStr=null; 070 071 /* 072 * Used to compare a attribute value returned from a search against this 073 * value which might have been defined in the ACI userattr rule. 074 */ 075 private String attrVal=null; 076 077 /* 078 * Contains the type of the userattr, one of the above enumerations. 079 */ 080 private UserAttrType userAttrType=null; 081 082 /* 083 * An enumeration representing the bind rule type. 084 */ 085 private EnumBindRuleType type=null; 086 087 /* 088 * The class used to hold the parent inheritance information. 089 */ 090 private ParentInheritance parentInheritance=null; 091 092 static { 093 /* 094 * Set up the filter used to search private and public contexts. 095 */ 096 try { 097 filter=SearchFilter.createFilterFromString("(objectclass=*)"); 098 } catch (DirectoryException ex) { 099 //TODO should never happen, error message? 100 } 101 } 102 103 /** 104 * Create an non-USERDN/GROUPDN instance of the userattr keyword class. 105 * @param attrStr The attribute name in string form. Kept in string form 106 * until processing. 107 * @param attrVal The attribute value in string form -- used in the USERDN 108 * evaluation for the parent hierarchy expression. 109 * @param userAttrType The userattr type of the rule 110 * "USERDN, GROUPDN, ...". 111 * @param type The bind rule type "=, !=". 112 */ 113 private UserAttr(String attrStr, String attrVal, UserAttrType userAttrType, 114 EnumBindRuleType type) { 115 this.attrStr=attrStr; 116 this.attrVal=attrVal; 117 this.userAttrType=userAttrType; 118 this.type=type; 119 } 120 121 /** 122 * Create an USERDN or GROUPDN instance of the userattr keyword class. 123 * @param userAttrType The userattr type of the rule (USERDN or GROUPDN) 124 * only. 125 * @param type The bind rule type "=, !=". 126 * @param parentInheritance The parent inheritance class to use for parent 127 * inheritance checks if any. 128 */ 129 private UserAttr(UserAttrType userAttrType, EnumBindRuleType type, 130 ParentInheritance parentInheritance) { 131 this.userAttrType=userAttrType; 132 this.type=type; 133 this.parentInheritance=parentInheritance; 134 } 135 /** 136 * Decode an string containing the userattr bind rule expression. 137 * @param expression The expression string. 138 * @param type The bind rule type. 139 * @return A class suitable for evaluating a userattr bind rule. 140 * @throws AciException If the string contains an invalid expression. 141 */ 142 public static KeywordBindRule decode(String expression, 143 EnumBindRuleType type) 144 throws AciException { 145 String[] vals=expression.split("#"); 146 if(vals.length != 2) { 147 Message message = 148 WARN_ACI_SYNTAX_INVALID_USERATTR_EXPRESSION.get(expression); 149 throw new AciException(message); 150 } 151 UserAttrType userAttrType=getType(vals[1]); 152 switch (userAttrType) { 153 case GROUPDN: 154 case USERDN: { 155 ParentInheritance parentInheritance = 156 new ParentInheritance(vals[0], false); 157 return new UserAttr (userAttrType, type, parentInheritance); 158 } 159 case ROLEDN: { 160 //The roledn keyword is not supported. Throw an exception with 161 //a message if it is seen in the expression. 162 Message message = 163 WARN_ACI_SYNTAX_ROLEDN_NOT_SUPPORTED.get(expression); 164 throw new AciException(message); 165 } 166 } 167 return new UserAttr(vals[0], vals[1], userAttrType, type); 168 } 169 170 /** 171 * Evaluate the expression using an evaluation context. 172 * @param evalCtx The evaluation context to use in the evaluation of the 173 * userattr expression. 174 * @return An enumeration containing the result of the evaluation. 175 */ 176 public EnumEvalResult evaluate(AciEvalContext evalCtx) { 177 EnumEvalResult matched; 178 //The working resource entry might be filtered and not have an 179 //attribute type that is needed to perform these evaluations. The 180 //evalCtx has a copy of the non-filtered entry, switch to it for these 181 //evaluations. 182 evalCtx.useFullResourceEntry(true); 183 switch(userAttrType) { 184 case ROLEDN: 185 case GROUPDN: 186 case USERDN: { 187 matched=evalDNKeywords(evalCtx); 188 break; 189 } 190 case URL: { 191 matched=evalURL(evalCtx); 192 break; 193 } 194 default: 195 matched=evalVAL(evalCtx); 196 } 197 //Switch back to the working resource entry. 198 evalCtx.useFullResourceEntry(false); 199 return matched; 200 } 201 202 /** Evaluate a VALUE userattr type. Look in client entry for an 203 * attribute value and in the resource entry for the same 204 * value. If both entries have the same value than return true. 205 * @param evalCtx The evaluation context to use. 206 * @return An enumeration containing the result of the 207 * evaluation. 208 */ 209 private EnumEvalResult evalVAL(AciEvalContext evalCtx) { 210 EnumEvalResult matched= EnumEvalResult.FALSE; 211 boolean undefined=false; 212 AttributeType attrType; 213 if((attrType = DirectoryServer.getAttributeType(attrStr)) == null) 214 attrType = DirectoryServer.getDefaultAttributeType(attrStr); 215 InternalClientConnection conn = 216 InternalClientConnection.getRootConnection(); 217 InternalSearchOperation op = 218 conn.processSearch(evalCtx.getClientDN(), 219 SearchScope.BASE_OBJECT, 220 DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false, 221 filter, null); 222 LinkedList<SearchResultEntry> result = op.getSearchEntries(); 223 if (!result.isEmpty()) { 224 AttributeValue val=new AttributeValue(attrType, attrVal); 225 SearchResultEntry resultEntry = result.getFirst(); 226 if(resultEntry.hasValue(attrType, null, val)) { 227 Entry e=evalCtx.getResourceEntry(); 228 if(e.hasValue(attrType, null, val)) 229 matched=EnumEvalResult.TRUE; 230 } 231 } 232 return matched.getRet(type, undefined); 233 } 234 235 /** 236 * Parses the substring after the '#' character to determine the userattr 237 * type. 238 * @param expr The string with the substring. 239 * @return An enumeration containing the type. 240 * @throws AciException If the substring contains an invalid type (roledn 241 * or groupdn). 242 */ 243 private static UserAttrType getType(String expr) throws AciException { 244 UserAttrType userAttrType; 245 if(expr.equalsIgnoreCase("userdn")) 246 userAttrType=UserAttrType.USERDN; 247 else if(expr.equalsIgnoreCase("groupdn")) { 248 userAttrType=UserAttrType.GROUPDN; 249 /* 250 Message message = WARN_ACI_SYNTAX_INVALID_USERATTR_KEYWORD.get( 251 "The groupdn userattr" + 252 "keyword is not supported."); 253 throw new AciException(message); 254 */ 255 } else if(expr.equalsIgnoreCase("roledn")) { 256 userAttrType=UserAttrType.ROLEDN; 257 /* 258 Message message = WARN_ACI_SYNTAX_INVALID_USERATTR_KEYWORD.get( 259 "The roledn userattr" + 260 "keyword is not supported."); 261 throw new AciException(message); 262 */ 263 } else if(expr.equalsIgnoreCase("ldapurl")) 264 userAttrType=UserAttrType.URL; 265 else 266 userAttrType=UserAttrType.VALUE; 267 return userAttrType; 268 } 269 270 /** 271 * Evaluate an URL userattr type. Look into the resource entry for the 272 * specified attribute and values. Assume it is an URL. Decode it an try 273 * and match it against the client entry attribute. 274 * @param evalCtx The evaluation context to evaluate with. 275 * @return An enumeration containing a result of the URL evaluation. 276 */ 277 private EnumEvalResult evalURL(AciEvalContext evalCtx) { 278 EnumEvalResult matched= EnumEvalResult.FALSE; 279 boolean undefined=false; 280 AttributeType attrType; 281 if((attrType = DirectoryServer.getAttributeType(attrStr)) == null) 282 attrType = DirectoryServer.getDefaultAttributeType(attrStr); 283 List<Attribute> attrs=evalCtx.getResourceEntry().getAttribute(attrType); 284 if(!attrs.isEmpty()) { 285 for(Attribute a : attrs) { 286 LinkedHashSet<AttributeValue> vals=a.getValues(); 287 for(AttributeValue v : vals) { 288 String urlStr=v.getStringValue(); 289 LDAPURL url; 290 try { 291 url=LDAPURL.decode(urlStr, true); 292 } catch (DirectoryException e) { 293 break; 294 } 295 matched=UserDN.evalURL(evalCtx, url); 296 if(matched != EnumEvalResult.FALSE) 297 break; 298 } 299 if(matched == EnumEvalResult.TRUE) 300 break; 301 if(matched == EnumEvalResult.ERR) { 302 undefined=true; 303 break; 304 } 305 } 306 } 307 return matched.getRet(type, undefined); 308 } 309 310 /** 311 * Evaluate the DN type userattr keywords. These are roledn, userdn and 312 * groupdn. The processing is the same for all three, although roledn is 313 * a slightly different. For the roledn userattr keyword, a very simple 314 * parent inheritance class was created. The rest of the processing is the 315 * same for all three keywords. 316 * 317 * @param evalCtx The evaluation context to evaluate with. 318 * @return An enumeration containing a result of the USERDN evaluation. 319 */ 320 private EnumEvalResult evalDNKeywords(AciEvalContext evalCtx) { 321 EnumEvalResult matched= EnumEvalResult.FALSE; 322 boolean undefined=false, stop=false; 323 int numLevels=parentInheritance.getNumLevels(); 324 int[] levels=parentInheritance.getLevels(); 325 AttributeType attrType=parentInheritance.getAttributeType(); 326 DN baseDN=parentInheritance.getBaseDN(); 327 if(baseDN != null) { 328 if (evalCtx.getResourceEntry().hasAttribute(attrType)) 329 matched=GroupDN.evaluate(evalCtx.getResourceEntry(), 330 evalCtx,attrType, baseDN); 331 } else { 332 for(int i=0;((i < numLevels) && !stop); i++ ) { 333 //The ROLEDN keyword will always enter this statement. The others 334 //might. For the add operation, the resource itself (level 0) 335 //must never be allowed to give access. 336 if(levels[i] == 0) { 337 if(evalCtx.isAddOperation()) { 338 undefined=true; 339 } else if (evalCtx.getResourceEntry().hasAttribute(attrType)) { 340 matched = 341 evalEntryAttr(evalCtx.getResourceEntry(), 342 evalCtx,attrType); 343 if(matched.equals(EnumEvalResult.TRUE)) 344 stop=true; 345 } 346 } else { 347 DN pDN= 348 getDNParentLevel(levels[i], evalCtx.getResourceDN()); 349 if(pDN == null) 350 continue; 351 LinkedHashSet<String> reqAttrs = new LinkedHashSet<String>(1); 352 reqAttrs.add(parentInheritance.getAttrTypeStr()); 353 InternalClientConnection conn = 354 InternalClientConnection.getRootConnection(); 355 InternalSearchOperation op = conn.processSearch(pDN, 356 SearchScope.BASE_OBJECT, 357 DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false, 358 filter, reqAttrs); 359 LinkedList<SearchResultEntry> result = 360 op.getSearchEntries(); 361 if (!result.isEmpty()) { 362 Entry e = result.getFirst(); 363 if(e.hasAttribute(attrType)) { 364 matched = evalEntryAttr(e, evalCtx, attrType); 365 if(matched.equals(EnumEvalResult.TRUE)) 366 stop=true; 367 } 368 } 369 } 370 } 371 } 372 return matched.getRet(type, undefined); 373 } 374 375 /** 376 * This method returns a parent DN based on the level. Not very 377 * sophisticated but it works. 378 * @param l The level. 379 * @param dn The DN to get the parent of. 380 * @return Parent DN based on the level or null if the level is greater 381 * than the rdn count. 382 */ 383 private DN getDNParentLevel(int l, DN dn) { 384 int rdns=dn.getNumComponents(); 385 if(l > rdns) 386 return null; 387 DN theDN=dn; 388 for(int i=0; i < l;i++) { 389 theDN=theDN.getParent(); 390 } 391 return theDN; 392 } 393 394 395 /** 396 * This method evaluates the user attribute type and calls the correct 397 * evalaution method. The three user attribute types that can be selected 398 * are USERDN or GROUPDN. 399 * 400 * @param e The entry to use in the evaluation. 401 * @param evalCtx The evaluation context to use in the evaluation. 402 * @param attributeType The attribute type to use in the evaluation. 403 * @return The result of the evaluation routine. 404 */ 405 private EnumEvalResult evalEntryAttr(Entry e, AciEvalContext evalCtx, 406 AttributeType attributeType) { 407 EnumEvalResult result=EnumEvalResult.FALSE; 408 switch (userAttrType) { 409 case USERDN: { 410 result=UserDN.evaluate(e, evalCtx.getClientDN(), 411 attributeType); 412 break; 413 } 414 case GROUPDN: { 415 result=GroupDN.evaluate(e, evalCtx, attributeType, null); 416 break; 417 } 418 } 419 return result; 420 } 421 422 }