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