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.*;
033 import org.opends.server.types.*;
034 import org.opends.server.core.DirectoryServer;
035
036 /**
037 * This class represents the userdn keyword in a bind rule.
038 */
039 public class UserDN implements KeywordBindRule {
040 /*
041 * A dummy URL for invalid URLs such as: all, parent, anyone, self.
042 */
043 private static String urlStr="ldap:///";
044
045 /*
046 * This list holds a list of objects representing a EnumUserDNType
047 * URL mapping.
048 */
049 private List<UserDNTypeURL> urlList=null;
050
051 /*
052 * Enumeration of the userdn operation type.
053 */
054 private EnumBindRuleType type=null;
055
056 /**
057 * Constructor that creates the userdn class. It also sets up an attribute
058 * type ("userdn") needed for wild-card matching.
059 * @param type The type of operation.
060 * @param urlList A list of enumerations containing the URL type and URL
061 * object that can be retrieved at evaluation time.
062 */
063 private UserDN(EnumBindRuleType type, List<UserDNTypeURL> urlList) {
064 this.type=type;
065 this.urlList=urlList;
066 }
067
068 /**
069 * Decodes an expression string representing a userdn bind rule.
070 * @param expression The string representation of the userdn bind rule
071 * expression.
072 * @param type An enumeration of the type of the bind rule.
073 * @return A KeywordBindRule class that represents the bind rule.
074 * @throws AciException If the expression failed to LDAP URL decode.
075 */
076 public static KeywordBindRule decode(String expression,
077 EnumBindRuleType type) throws AciException {
078
079 String[] vals=expression.split("[|][|]");
080 List<UserDNTypeURL> urlList = new LinkedList<UserDNTypeURL>();
081 for(int i=0, m=vals.length; i < m; i++)
082 {
083 StringBuilder value = new StringBuilder(vals[i].trim());
084 /*
085 * TODO Evaluate using a wild-card in the dn portion of LDAP url.
086 * The current implementation (DS6) does not treat a "*"
087 * as a wild-card.
088 *
089 * Is it allowed to have a full LDAP URL (i.e., including a base,
090 * scope, and filter) in which the base DN contains asterisks to
091 * make it a wildcard? If so, then I don't think that the current
092 * implementation handles that correctly. It will probably fail
093 * when attempting to create the LDAP URL because the base DN isn't a
094 * valid DN.
095 */
096 EnumUserDNType userDNType = UserDN.getType(value);
097 LDAPURL url;
098 try {
099 url=LDAPURL.decode(value.toString(), true);
100 } catch (DirectoryException de) {
101 Message message = WARN_ACI_SYNTAX_INVALID_USERDN_URL.get(
102 de.getMessageObject());
103 throw new AciException(message);
104 }
105 UserDNTypeURL dnTypeURL=new UserDNTypeURL(userDNType, url);
106 urlList.add(dnTypeURL);
107 }
108 return new UserDN(type, urlList);
109 }
110
111 /**
112 * This method determines the type of the DN (suffix in URL terms)
113 * part of a URL, by examining the full URL itself for known strings
114 * such as (corresponding type shown in parenthesis)
115 *
116 * "ldap:///anyone" (EnumUserDNType.ANYONE)
117 * "ldap:///parent" (EnumUserDNType.PARENT)
118 * "ldap:///all" (EnumUserDNType.ALL)
119 * "ldap:///self" (EnumUserDNType.SELF)
120 *
121 * If one of the four above are found, the URL is replaced with a dummy
122 * pattern "ldap:///". This is done because the above four are invalid
123 * URLs; but the syntax is valid for an userdn keyword expression. The
124 * dummy URLs are never used.
125 *
126 * If none of the above are found, it determine if the URL DN is a
127 * substring pattern, such as:
128 *
129 * "ldap:///uid=*, dc=example, dc=com" (EnumUserDNType.PATTERN)
130 *
131 * If none of the above are determined, it checks if the URL
132 * is a complete URL with scope and filter defined:
133 *
134 * "ldap:///uid=test,dc=example,dc=com??sub?(cn=j*)" (EnumUserDNType.URL)
135 *
136 * If none of these those types can be identified, it defaults to
137 * EnumUserDNType.DN.
138 *
139 * @param bldr A string representation of the URL that can be modified.
140 * @return The user DN type of the URL.
141 */
142 private static EnumUserDNType getType(StringBuilder bldr) {
143 EnumUserDNType type;
144 String str=bldr.toString();
145
146 if(str.indexOf("?") != -1) {
147 type = EnumUserDNType.URL;
148 } else if(str.equalsIgnoreCase("ldap:///self")) {
149 type = EnumUserDNType.SELF;
150 bldr.replace(0, bldr.length(), urlStr);
151 } else if(str.equalsIgnoreCase("ldap:///anyone")) {
152 type = EnumUserDNType.ANYONE;
153 bldr.replace(0, bldr.length(), urlStr);
154 } else if(str.equalsIgnoreCase("ldap:///parent")) {
155 type = EnumUserDNType.PARENT;
156 bldr.replace(0, bldr.length(), urlStr);
157 } else if(str.equalsIgnoreCase("ldap:///all")) {
158 type = EnumUserDNType.ALL;
159 bldr.replace(0, bldr.length(), urlStr);
160 } else if(str.indexOf("*") != -1) {
161 type = EnumUserDNType.DNPATTERN;
162 } else {
163 type = EnumUserDNType.DN;
164 }
165 return type;
166 }
167
168 /**
169 * Performs the evaluation of a userdn bind rule based on the
170 * evaluation context passed to it. The evaluation stops when there
171 * are no more UserDNTypeURLs to evaluate or if an UserDNTypeURL
172 * evaluates to true.
173 * @param evalCtx The evaluation context to evaluate with.
174 * @return An evaluation result enumeration containing the result
175 * of the evaluation.
176 */
177 public EnumEvalResult evaluate(AciEvalContext evalCtx) {
178 EnumEvalResult matched = EnumEvalResult.FALSE;
179 boolean undefined=false;
180
181 boolean isAnonUser=evalCtx.isAnonymousUser();
182 Iterator<UserDNTypeURL> it=urlList.iterator();
183 for(; it.hasNext() && matched != EnumEvalResult.TRUE &&
184 matched != EnumEvalResult.ERR;) {
185 UserDNTypeURL dnTypeURL=it.next();
186 //Handle anonymous checks here
187 if(isAnonUser) {
188 if(dnTypeURL.getUserDNType() == EnumUserDNType.ANYONE)
189 matched = EnumEvalResult.TRUE;
190 } else
191 matched=evalNonAnonymous(evalCtx, dnTypeURL);
192 }
193 return matched.getRet(type, undefined);
194 }
195
196 /**
197 * Performs an evaluation of a single UserDNTypeURL of a userdn bind
198 * rule using the evaluation context provided. This method is called
199 * for the non-anonymous user case.
200 * @param evalCtx The evaluation context to evaluate with.
201 * @param dnTypeURL The URL dn type mapping to evaluate.
202 * @return An evaluation result enumeration containing the result
203 * of the evaluation.
204 */
205 private EnumEvalResult evalNonAnonymous(AciEvalContext evalCtx,
206 UserDNTypeURL dnTypeURL) {
207 DN clientDN=evalCtx.getClientDN();
208 DN resDN=evalCtx.getResourceDN();
209 EnumEvalResult matched = EnumEvalResult.FALSE;
210 EnumUserDNType type=dnTypeURL.getUserDNType();
211 LDAPURL url=dnTypeURL.getURL();
212 switch (type) {
213 case URL:
214 {
215 matched = evalURL(evalCtx, url);
216 break;
217 }
218 case ANYONE:
219 {
220 matched = EnumEvalResult.TRUE;
221 break;
222 }
223 case SELF:
224 {
225 if (clientDN.equals(resDN)) matched = EnumEvalResult.TRUE;
226 break;
227 }
228 case PARENT:
229 {
230 DN parentDN = resDN.getParent();
231 if ((parentDN != null) &&
232 (parentDN.equals(clientDN)))
233 matched = EnumEvalResult.TRUE;
234 break;
235 }
236 case ALL:
237 {
238 matched = EnumEvalResult.TRUE;
239 break;
240 }
241 case DNPATTERN:
242 {
243 matched = evalDNPattern(evalCtx, url);
244 break;
245 }
246 case DN:
247 {
248 try
249 {
250 DN dn = url.getBaseDN();
251 if (clientDN.equals(dn))
252 matched = EnumEvalResult.TRUE;
253 else {
254 //This code handles the case where a root dn entry does
255 //not have bypass-acl privilege and the ACI bind rule
256 //userdn DN possible is an alternate root DN.
257 DN actualDN=DirectoryServer.getActualRootBindDN(dn);
258 DN clientActualDN=
259 DirectoryServer.getActualRootBindDN(clientDN);
260 if(actualDN != null)
261 dn=actualDN;
262 if(clientActualDN != null)
263 clientDN=clientActualDN;
264 if(clientDN.equals(dn))
265 matched=EnumEvalResult.TRUE;
266 }
267 } catch (DirectoryException ex) {
268 //TODO add message
269 }
270 }
271 }
272 return matched;
273 }
274
275 /**
276 * This method evaluates a DN pattern userdn expression.
277 * @param evalCtx The evaluation context to use.
278 * @param url The LDAP URL containing the pattern.
279 * @return An enumeration evaluation result.
280 */
281 private EnumEvalResult evalDNPattern(AciEvalContext evalCtx, LDAPURL url) {
282 PatternDN pattern;
283 try {
284 pattern = PatternDN.decode(url.getRawBaseDN());
285 } catch (DirectoryException ex) {
286 return EnumEvalResult.FALSE;
287 }
288
289 return pattern.matchesDN(evalCtx.getClientDN()) ?
290 EnumEvalResult.TRUE : EnumEvalResult.FALSE;
291 }
292
293
294 /**
295 * This method evaluates an URL userdn expression. Something like:
296 * ldap:///suffix??sub?(filter). It also searches for the client DN
297 * entry and saves it in the evaluation context for repeat evaluations
298 * that might come later in processing.
299 *
300 * @param evalCtx The evaluation context to use.
301 * @param url URL containing the URL to use in the evaluation.
302 * @return An enumeration of the evaluation result.
303 */
304 public static EnumEvalResult evalURL(AciEvalContext evalCtx, LDAPURL url) {
305 EnumEvalResult ret=EnumEvalResult.FALSE;
306 DN urlDN;
307 SearchFilter filter;
308 try {
309 urlDN=url.getBaseDN();
310 filter=url.getFilter();
311 } catch (DirectoryException ex) {
312 return EnumEvalResult.FALSE;
313 }
314 SearchScope scope=url.getScope();
315 if(scope == SearchScope.WHOLE_SUBTREE) {
316 if(!evalCtx.getClientDN().isDescendantOf(urlDN))
317 return EnumEvalResult.FALSE;
318 } else if(scope == SearchScope.SINGLE_LEVEL) {
319 DN parent=evalCtx.getClientDN().getParent();
320 if((parent != null) && !parent.equals(urlDN))
321 return EnumEvalResult.FALSE;
322 } else if(scope == SearchScope.SUBORDINATE_SUBTREE) {
323 DN userDN = evalCtx.getClientDN();
324 if ((userDN.getNumComponents() <= urlDN.getNumComponents()) ||
325 !userDN.isDescendantOf(urlDN)) {
326 return EnumEvalResult.FALSE;
327 }
328 } else {
329 if(!evalCtx.getClientDN().equals(urlDN))
330 return EnumEvalResult.FALSE;
331 }
332 try {
333 if(filter.matchesEntry(evalCtx.getClientEntry()))
334 ret=EnumEvalResult.TRUE;
335 } catch (DirectoryException ex) {
336 return EnumEvalResult.FALSE;
337 }
338 return ret;
339 }
340
341 /*
342 * TODO Evaluate making this method more efficient.
343 *
344 * The evalDNEntryAttr method isn't as efficient as it could be. It would
345 * probably be faster to to convert the clientDN to an AttributeValue and
346 * see if the entry has that value than to decode each value as a DN and
347 * see if it matches the clientDN.
348 */
349 /**
350 * This method searches an entry for an attribute value that is
351 * treated as a DN. That DN is then compared against the client
352 * DN.
353 * @param e The entry to get the attribute type from.
354 * @param clientDN The client authorization DN to check for.
355 * @param attrType The attribute type from the bind rule.
356 * @return An enumeration with the result.
357 */
358 public static EnumEvalResult evaluate(Entry e, DN clientDN,
359 AttributeType attrType) {
360 EnumEvalResult matched= EnumEvalResult.FALSE;
361 List<Attribute> attrs = e.getAttribute(attrType);
362 LinkedHashSet<AttributeValue> vals = attrs.get(0).getValues();
363 for(AttributeValue v : vals) {
364 try {
365 DN dn=DN.decode(v.getStringValue());
366 if(dn.equals(clientDN)) {
367 matched=EnumEvalResult.TRUE;
368 break;
369 }
370 } catch (DirectoryException ex) {
371 break;
372 }
373 }
374 return matched;
375 }
376 }