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    }