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 legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2008 Sun Microsystems, Inc.
025 *      Portions Copyright 2011-2015 ForgeRock AS
026 */
027package org.opends.server.authorization.dseecompat;
028
029import java.util.LinkedList;
030import java.util.List;
031
032import org.forgerock.i18n.LocalizableMessage;
033import org.forgerock.opendj.ldap.ByteString;
034import org.forgerock.opendj.ldap.SearchScope;
035import org.opends.server.core.DirectoryServer;
036import org.opends.server.protocols.internal.InternalSearchOperation;
037import org.opends.server.protocols.internal.SearchRequest;
038import org.opends.server.types.*;
039
040import static org.opends.messages.AccessControlMessages.*;
041import static org.opends.server.protocols.internal.InternalClientConnection.*;
042import static org.opends.server.protocols.internal.Requests.*;
043
044/*
045 * TODO Evaluate making this class more efficient.
046 *
047 * This class isn't as efficient as it could be.  For example, the evalVAL()
048 * method should be able to use cached versions of the attribute type and
049 * filter. The evalURL() and evalDN() methods should also be able to use a
050 * cached version of the attribute type.
051 */
052/**
053 * This class implements the  userattr bind rule keyword.
054 */
055public class UserAttr implements KeywordBindRule {
056
057    /**
058     * This enumeration is the various types the userattr can have after
059     * the "#" token.
060     */
061    private enum UserAttrType {
062        USERDN, GROUPDN, ROLEDN, URL, VALUE;
063
064        private static UserAttrType getType(String expr) throws AciException {
065            if("userdn".equalsIgnoreCase(expr)) {
066                return UserAttrType.USERDN;
067            } else if("groupdn".equalsIgnoreCase(expr)) {
068                 return UserAttrType.GROUPDN;
069            } else if("roledn".equalsIgnoreCase(expr)) {
070                return UserAttrType.ROLEDN;
071            } else if("ldapurl".equalsIgnoreCase(expr)) {
072                return UserAttrType.URL;
073            }
074            return UserAttrType.VALUE;
075        }
076    }
077
078    /**
079     * Used to create an attribute type that can compare the value below in
080     * an entry returned from an internal search.
081     */
082    private String attrStr;
083
084    /**
085     * Used to compare a attribute value returned from a search against this
086     * value which might have been defined in the ACI userattr rule.
087     */
088    private String attrVal;
089
090    /** Contains the type of the userattr, one of the above enumerations. */
091    private UserAttrType userAttrType;
092
093    /** An enumeration representing the bind rule type. */
094    private EnumBindRuleType type;
095
096    /** The class used to hold the parent inheritance information. */
097    private ParentInheritance parentInheritance;
098
099    /**
100     * Create an non-USERDN/GROUPDN instance of the userattr keyword class.
101     * @param attrStr The attribute name in string form. Kept in string form
102     * until processing.
103     * @param attrVal The attribute value in string form -- used in the USERDN
104     * evaluation for the parent hierarchy expression.
105     * @param userAttrType The userattr type of the rule
106     * "USERDN, GROUPDN, ...".
107     * @param type The bind rule type "=, !=".
108     */
109    private UserAttr(String attrStr, String attrVal, UserAttrType userAttrType,
110            EnumBindRuleType type) {
111        this.attrStr=attrStr;
112        this.attrVal=attrVal;
113        this.userAttrType=userAttrType;
114        this.type=type;
115    }
116
117    /**
118     * Create an USERDN or GROUPDN  instance of the userattr keyword class.
119     * @param userAttrType The userattr type of the rule (USERDN or GROUPDN)
120     * only.
121     * @param type The bind rule type "=, !=".
122     * @param parentInheritance The parent inheritance class to use for parent
123     * inheritance checks if any.
124     */
125    private UserAttr(UserAttrType userAttrType, EnumBindRuleType type,
126                     ParentInheritance parentInheritance) {
127        this.userAttrType=userAttrType;
128        this.type=type;
129        this.parentInheritance=parentInheritance;
130    }
131    /**
132     * Decode an string containing the userattr bind rule expression.
133     * @param expression The expression string.
134     * @param type The bind rule type.
135     * @return A class suitable for evaluating a userattr bind rule.
136     * @throws AciException If the string contains an invalid expression.
137     */
138    public static KeywordBindRule decode(String expression,
139                                         EnumBindRuleType type)
140    throws AciException {
141        String[] vals=expression.split("#");
142        if(vals.length != 2) {
143            LocalizableMessage message =
144                WARN_ACI_SYNTAX_INVALID_USERATTR_EXPRESSION.get(expression);
145            throw new AciException(message);
146        }
147        UserAttrType userAttrType = UserAttrType.getType(vals[1]);
148        switch (userAttrType) {
149                case GROUPDN:
150                case USERDN: {
151                    ParentInheritance parentInheritance =
152                            new ParentInheritance(vals[0], false);
153                    return new UserAttr (userAttrType, type, parentInheritance);
154                }
155                case ROLEDN: {
156                  //The roledn keyword is not supported. Throw an exception with
157                  //a message if it is seen in the expression.
158                  throw new AciException(WARN_ACI_SYNTAX_ROLEDN_NOT_SUPPORTED.get(expression));
159                }
160         }
161         return new UserAttr(vals[0], vals[1], userAttrType, type);
162    }
163
164    /**
165     * Evaluate the expression using an evaluation context.
166     * @param evalCtx   The evaluation context to use in the evaluation of the
167     * userattr expression.
168     * @return  An enumeration containing the result of the evaluation.
169     */
170    @Override
171    public EnumEvalResult evaluate(AciEvalContext evalCtx) {
172      EnumEvalResult matched;
173      //The working resource entry might be filtered and not have an
174      //attribute type that is needed to perform these evaluations. The
175      //evalCtx has a copy of the non-filtered entry, switch to it for these
176      //evaluations.
177      switch(userAttrType) {
178      case ROLEDN:
179      case GROUPDN:
180      case USERDN: {
181        matched=evalDNKeywords(evalCtx);
182        break;
183      }
184      case URL: {
185        matched=evalURL(evalCtx);
186        break;
187      }
188      default:
189        matched=evalVAL(evalCtx);
190      }
191      return matched;
192    }
193
194    /** Evaluate a VALUE userattr type. Look in client entry for an
195     *  attribute value and in the resource entry for the same
196     *  value. If both entries have the same value than return true.
197     * @param evalCtx The evaluation context to use.
198     * @return An enumeration containing the result of the
199     * evaluation.
200     */
201    private EnumEvalResult evalVAL(AciEvalContext evalCtx) {
202        EnumEvalResult matched= EnumEvalResult.FALSE;
203        boolean undefined=false;
204        AttributeType attrType = DirectoryServer.getAttributeTypeOrDefault(attrStr);
205        final SearchRequest request = newSearchRequest(evalCtx.getClientDN(), SearchScope.BASE_OBJECT);
206        InternalSearchOperation op = getRootConnection().processSearch(request);
207        LinkedList<SearchResultEntry> result = op.getSearchEntries();
208        if (!result.isEmpty()) {
209            ByteString val= ByteString.valueOfUtf8(attrVal);
210            SearchResultEntry resultEntry = result.getFirst();
211            if(resultEntry.hasValue(attrType, null, val)) {
212                Entry e=evalCtx.getResourceEntry();
213                if(e.hasValue(attrType, null, val))
214                {
215                    matched=EnumEvalResult.TRUE;
216                }
217            }
218        }
219        return matched.getRet(type, undefined);
220    }
221
222    /**
223     * Evaluate an URL userattr type. Look into the resource entry for the
224     * specified attribute and values. Assume it is an URL. Decode it an try
225     * and match it against the client entry attribute.
226     * @param evalCtx  The evaluation context to evaluate with.
227     * @return An enumeration containing a result of the URL evaluation.
228     */
229    private EnumEvalResult evalURL(AciEvalContext evalCtx) {
230        EnumEvalResult matched= EnumEvalResult.FALSE;
231        boolean undefined=false;
232        AttributeType attrType = DirectoryServer.getAttributeTypeOrDefault(attrStr);
233        List<Attribute> attrs=evalCtx.getResourceEntry().getAttribute(attrType);
234        if(!attrs.isEmpty()) {
235            for(Attribute a : attrs) {
236                for(ByteString v : a) {
237                    LDAPURL url;
238                    try {
239                       url = LDAPURL.decode(v.toString(), true);
240                    } catch (DirectoryException e) {
241                        break;
242                    }
243                    matched=UserDN.evalURL(evalCtx, url);
244                    if(matched != EnumEvalResult.FALSE)
245                    {
246                        break;
247                    }
248                }
249                if (matched == EnumEvalResult.TRUE)
250                {
251                    break;
252                }
253                if (matched == EnumEvalResult.ERR)
254                {
255                    undefined=true;
256                    break;
257                }
258            }
259        }
260        return matched.getRet(type, undefined);
261    }
262
263    /**
264     * Evaluate the DN type userattr keywords. These are roledn, userdn and
265     * groupdn. The processing is the same for all three, although roledn is
266     * a slightly different. For the roledn userattr keyword, a very simple
267     * parent inheritance class was created. The rest of the processing is the
268     * same for all three keywords.
269     *
270     * @param evalCtx The evaluation context to evaluate with.
271     * @return An enumeration containing a result of the USERDN evaluation.
272     */
273    private EnumEvalResult evalDNKeywords(AciEvalContext evalCtx) {
274        EnumEvalResult matched= EnumEvalResult.FALSE;
275        boolean undefined=false, stop=false;
276        int numLevels=parentInheritance.getNumLevels();
277        int[] levels=parentInheritance.getLevels();
278        AttributeType attrType=parentInheritance.getAttributeType();
279        DN baseDN=parentInheritance.getBaseDN();
280        if(baseDN != null) {
281            if (evalCtx.getResourceEntry().hasAttribute(attrType)) {
282                matched=GroupDN.evaluate(evalCtx.getResourceEntry(),
283                        evalCtx,attrType, baseDN);
284            }
285        } else {
286        for(int i=0;(i < numLevels && !stop); i++ ) {
287            //The ROLEDN keyword will always enter this statement. The others
288            //might. For the add operation, the resource itself (level 0)
289            //must never be allowed to give access.
290            if(levels[i] == 0) {
291                if(evalCtx.isAddOperation()) {
292                    undefined=true;
293                } else if (evalCtx.getResourceEntry().hasAttribute(attrType)) {
294                    matched =
295                            evalEntryAttr(evalCtx.getResourceEntry(),
296                                    evalCtx,attrType);
297                    if(matched.equals(EnumEvalResult.TRUE)) {
298                        stop=true;
299                    }
300                }
301            } else {
302                DN pDN = getDNParentLevel(levels[i], evalCtx.getResourceDN());
303                if(pDN == null) {
304                    continue;
305                }
306                final SearchRequest request = newSearchRequest(pDN, SearchScope.BASE_OBJECT)
307                    .addAttribute(parentInheritance.getAttrTypeStr());
308                InternalSearchOperation op = getRootConnection().processSearch(request);
309                LinkedList<SearchResultEntry> result = op.getSearchEntries();
310                if (!result.isEmpty()) {
311                    Entry e = result.getFirst();
312                    if(e.hasAttribute(attrType)) {
313                        matched = evalEntryAttr(e, evalCtx, attrType);
314                        if(matched.equals(EnumEvalResult.TRUE)) {
315                            stop=true;
316                        }
317                    }
318                }
319            }
320        }
321    }
322    return matched.getRet(type, undefined);
323    }
324
325    /**
326     * This method returns a parent DN based on the level. Not very
327     * sophisticated but it works.
328     * @param l The level.
329     * @param dn The DN to get the parent of.
330     * @return Parent DN based on the level or null if the level is greater
331     * than the  rdn count.
332     */
333    private DN getDNParentLevel(int l, DN dn) {
334        int rdns=dn.size();
335        if(l > rdns) {
336            return null;
337        }
338        DN theDN=dn;
339        for(int i=0; i < l;i++) {
340            theDN=theDN.parent();
341        }
342        return theDN;
343    }
344
345
346    /**
347     * This method evaluates the user attribute type and calls the correct
348     * evalaution method. The three user attribute types that can be selected
349     * are USERDN or GROUPDN.
350     *
351     * @param e The entry to use in the evaluation.
352     * @param evalCtx The evaluation context to use in the evaluation.
353     * @param attributeType The attribute type to use in the evaluation.
354     * @return The result of the evaluation routine.
355     */
356    private EnumEvalResult evalEntryAttr(Entry e, AciEvalContext evalCtx,
357                                         AttributeType attributeType) {
358        EnumEvalResult result=EnumEvalResult.FALSE;
359        switch (userAttrType) {
360            case USERDN: {
361                result=UserDN.evaluate(e, evalCtx.getClientDN(),
362                                       attributeType);
363                break;
364            }
365            case GROUPDN: {
366                result=GroupDN.evaluate(e, evalCtx, attributeType, null);
367                break;
368            }
369        }
370        return result;
371    }
372
373    /** {@inheritDoc} */
374    @Override
375    public String toString()
376    {
377        final StringBuilder sb = new StringBuilder();
378        toString(sb);
379        return sb.toString();
380    }
381
382    /** {@inheritDoc} */
383    @Override
384    public final void toString(StringBuilder buffer)
385    {
386        buffer.append(super.toString());
387    }
388
389}