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