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    }