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