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