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 }