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 static org.opends.messages.AccessControlMessages.*;
020import static org.opends.server.protocols.internal.InternalClientConnection.*;
021import static org.opends.server.protocols.internal.Requests.*;
022
023import java.util.LinkedList;
024import java.util.List;
025
026import org.forgerock.i18n.LocalizableMessage;
027import org.forgerock.i18n.LocalizedIllegalArgumentException;
028import org.forgerock.opendj.ldap.ByteString;
029import org.forgerock.opendj.ldap.DN;
030import org.forgerock.opendj.ldap.SearchScope;
031import org.forgerock.opendj.ldap.schema.AttributeType;
032import org.opends.server.core.DirectoryServer;
033import org.opends.server.protocols.internal.InternalSearchOperation;
034import org.opends.server.protocols.internal.SearchRequest;
035import org.opends.server.types.Attribute;
036import org.opends.server.types.DirectoryException;
037import org.opends.server.types.Entry;
038import org.opends.server.types.LDAPURL;
039import org.opends.server.types.SearchResultEntry;
040
041/**
042 * This class implements the  userattr bind rule keyword.
043 * <p>
044 * TODO Evaluate making this class more efficient.
045 *<p>
046 * This class isn't as efficient as it could be.  For example, the {@link #evalVAL(AciEvalContext)}
047 * method should be able to use cached versions of the attribute type and filter.
048 * The {@link #evalURL(AciEvalContext)} and {@link #evalDNKeywords(AciEvalContext)}
049 * methods should also be able to use a cached version of the attribute type.
050 */
051public class UserAttr implements KeywordBindRule {
052    /** This enumeration is the various types the userattr can have after the "#" token. */
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 final String attrStr;
075    /**
076     * Used to compare a attribute value returned from a search against this
077     * value which might have been defined in the ACI userattr rule.
078     */
079    private final String attrVal;
080    /** Contains the type of the userattr, one of the above enumerations. */
081    private final UserAttrType userAttrType;
082    /** An enumeration representing the bind rule type. */
083    private final EnumBindRuleType type;
084    /** The class used to hold the parent inheritance information. */
085    private final ParentInheritance parentInheritance;
086
087    /**
088     * Create an non-USERDN/GROUPDN instance of the userattr keyword class.
089     * @param attrStr The attribute name in string form. Kept in string form
090     * until processing.
091     * @param attrVal The attribute value in string form -- used in the USERDN
092     * evaluation for the parent hierarchy expression.
093     * @param userAttrType The userattr type of the rule
094     * "USERDN, GROUPDN, ...".
095     * @param type The bind rule type "=, !=".
096     */
097    private UserAttr(String attrStr, String attrVal, UserAttrType userAttrType,
098            EnumBindRuleType type) {
099        this.attrStr=attrStr;
100        this.attrVal=attrVal;
101        this.userAttrType=userAttrType;
102        this.type=type;
103        this.parentInheritance = null;
104    }
105
106    /**
107     * Create an USERDN or GROUPDN  instance of the userattr keyword class.
108     * @param userAttrType The userattr type of the rule (USERDN or GROUPDN)
109     * only.
110     * @param type The bind rule type "=, !=".
111     * @param parentInheritance The parent inheritance class to use for parent
112     * inheritance checks if any.
113     */
114    private UserAttr(UserAttrType userAttrType, EnumBindRuleType type,
115                     ParentInheritance parentInheritance) {
116        this.attrStr = null;
117        this.attrVal = null;
118        this.userAttrType=userAttrType;
119        this.type=type;
120        this.parentInheritance=parentInheritance;
121    }
122    /**
123     * Decode an string containing the userattr bind rule expression.
124     * @param expression The expression string.
125     * @param type The bind rule type.
126     * @return A class suitable for evaluating a userattr bind rule.
127     * @throws AciException If the string contains an invalid expression.
128     */
129    public static KeywordBindRule decode(String expression,
130                                         EnumBindRuleType type)
131    throws AciException {
132        String[] vals=expression.split("#");
133        if(vals.length != 2) {
134            LocalizableMessage message =
135                WARN_ACI_SYNTAX_INVALID_USERATTR_EXPRESSION.get(expression);
136            throw new AciException(message);
137        }
138        UserAttrType userAttrType = UserAttrType.getType(vals[1]);
139        switch (userAttrType) {
140                case GROUPDN:
141                case USERDN: {
142                    ParentInheritance parentInheritance =
143                            new ParentInheritance(vals[0], false);
144                    return new UserAttr (userAttrType, type, parentInheritance);
145                }
146                case ROLEDN: {
147                  //The roledn keyword is not supported. Throw an exception with
148                  //a message if it is seen in the expression.
149                  throw new AciException(WARN_ACI_SYNTAX_ROLEDN_NOT_SUPPORTED.get(expression));
150                }
151         }
152         return new UserAttr(vals[0], vals[1], userAttrType, type);
153    }
154
155    /**
156     * Evaluate the expression using an evaluation context.
157     * @param evalCtx   The evaluation context to use in the evaluation of the
158     * userattr expression.
159     * @return  An enumeration containing the result of the evaluation.
160     */
161    @Override
162    public EnumEvalResult evaluate(AciEvalContext evalCtx) {
163      //The working resource entry might be filtered and not have an
164      //attribute type that is needed to perform these evaluations. The
165      //evalCtx has a copy of the non-filtered entry, switch to it for these
166      //evaluations.
167      switch(userAttrType) {
168      case ROLEDN:
169      case GROUPDN:
170      case USERDN:
171        return evalDNKeywords(evalCtx);
172      case URL:
173        return evalURL(evalCtx);
174      default:
175        return evalVAL(evalCtx);
176      }
177    }
178
179    /** Evaluate a VALUE userattr type. Look in client entry for an
180     *  attribute value and in the resource entry for the same
181     *  value. If both entries have the same value than return true.
182     * @param evalCtx The evaluation context to use.
183     * @return An enumeration containing the result of the
184     * evaluation.
185     */
186    private EnumEvalResult evalVAL(AciEvalContext evalCtx) {
187        EnumEvalResult matched= EnumEvalResult.FALSE;
188        boolean undefined=false;
189        AttributeType attrType = DirectoryServer.getSchema().getAttributeType(attrStr);
190        final SearchRequest request = newSearchRequest(evalCtx.getClientDN(), SearchScope.BASE_OBJECT);
191        InternalSearchOperation op = getRootConnection().processSearch(request);
192        LinkedList<SearchResultEntry> result = op.getSearchEntries();
193        if (!result.isEmpty()) {
194            ByteString val= ByteString.valueOfUtf8(attrVal);
195            SearchResultEntry resultEntry = result.getFirst();
196            if(resultEntry.hasValue(attrType, val)) {
197                Entry e=evalCtx.getResourceEntry();
198                if(e.hasValue(attrType, val))
199                {
200                    matched=EnumEvalResult.TRUE;
201                }
202            }
203        }
204        return matched.getRet(type, undefined);
205    }
206
207    /**
208     * Evaluate an URL userattr type. Look into the resource entry for the
209     * specified attribute and values. Assume it is an URL. Decode it an try
210     * and match it against the client entry attribute.
211     * @param evalCtx  The evaluation context to evaluate with.
212     * @return An enumeration containing a result of the URL evaluation.
213     */
214    private EnumEvalResult evalURL(AciEvalContext evalCtx) {
215        EnumEvalResult matched= EnumEvalResult.FALSE;
216        AttributeType attrType = DirectoryServer.getSchema().getAttributeType(attrStr);
217        List<Attribute> attrs=evalCtx.getResourceEntry().getAttribute(attrType);
218        for(Attribute a : attrs) {
219            for(ByteString v : a) {
220                LDAPURL url;
221                try {
222                   url = LDAPURL.decode(v.toString(), true);
223                } catch (LocalizedIllegalArgumentException | DirectoryException e) {
224                    break;
225                }
226                matched=UserDN.evalURL(evalCtx, url);
227                if(matched != EnumEvalResult.FALSE)
228                {
229                    break;
230                }
231            }
232            if (matched == EnumEvalResult.TRUE)
233            {
234                break;
235            }
236        }
237        return matched.getRet(type, matched == EnumEvalResult.ERR);
238    }
239
240    /**
241     * Evaluate the DN type userattr keywords. These are roledn, userdn and
242     * groupdn. The processing is the same for all three, although roledn is
243     * a slightly different. For the roledn userattr keyword, a very simple
244     * parent inheritance class was created. The rest of the processing is the
245     * same for all three keywords.
246     *
247     * @param evalCtx The evaluation context to evaluate with.
248     * @return An enumeration containing a result of the USERDN evaluation.
249     */
250    private EnumEvalResult evalDNKeywords(AciEvalContext evalCtx) {
251        boolean matched = false;
252        boolean undefined = false;
253        int numLevels=parentInheritance.getNumLevels();
254        int[] levels=parentInheritance.getLevels();
255        AttributeType attrType=parentInheritance.getAttributeType();
256        DN baseDN=parentInheritance.getBaseDN();
257        Entry resourceEntry = evalCtx.getResourceEntry();
258        if(baseDN != null) {
259            matched = resourceEntry.hasAttribute(attrType) && GroupDN.evaluate(resourceEntry, evalCtx,attrType, baseDN);
260        } else {
261        for (int i = 0; i < numLevels; i++) {
262            //The ROLEDN keyword will always enter this statement. The others
263            //might. For the add operation, the resource itself (level 0)
264            //must never be allowed to give access.
265            if(levels[i] == 0) {
266                if(evalCtx.isAddOperation()) {
267                    undefined=true;
268                } else if (resourceEntry.hasAttribute(attrType)
269                        && evalEntryAttr(resourceEntry, evalCtx, attrType)) {
270                    matched = true;
271                    break;
272                }
273            } else {
274                DN pDN = evalCtx.getResourceDN().parent(levels[i]);
275                if(pDN == null) {
276                    continue;
277                }
278                final SearchRequest request = newSearchRequest(pDN, SearchScope.BASE_OBJECT)
279                    .addAttribute(parentInheritance.getAttrTypeStr());
280                InternalSearchOperation op = getRootConnection().processSearch(request);
281                LinkedList<SearchResultEntry> result = op.getSearchEntries();
282                if (!result.isEmpty()) {
283                    Entry e = result.getFirst();
284                    if (e.hasAttribute(attrType) && evalEntryAttr(e, evalCtx, attrType)) {
285                        matched = true;
286                        break;
287                    }
288                }
289            }
290        }
291        }
292        EnumEvalResult res = matched ? EnumEvalResult.TRUE : EnumEvalResult.FALSE;
293        return res.getRet(type, undefined);
294    }
295
296    /**
297     * This method evaluates the user attribute type and calls the correct
298     * evaluation method. The three user attribute types that can be selected
299     * are USERDN or GROUPDN.
300     *
301     * @param e The entry to use in the evaluation.
302     * @param evalCtx The evaluation context to use in the evaluation.
303     * @param attributeType The attribute type to use in the evaluation.
304     * @return The result of the evaluation routine.
305     */
306    private boolean evalEntryAttr(Entry e, AciEvalContext evalCtx, AttributeType attributeType) {
307        switch (userAttrType) {
308        case USERDN:
309            return UserDN.evaluate(e, evalCtx.getClientDN(), attributeType);
310        case GROUPDN:
311            return GroupDN.evaluate(e, evalCtx, attributeType, null);
312        default:
313            return false;
314        }
315    }
316
317    @Override
318    public String toString()
319    {
320        final StringBuilder sb = new StringBuilder();
321        toString(sb);
322        return sb.toString();
323    }
324
325    @Override
326    public final void toString(StringBuilder buffer)
327    {
328        buffer.append(super.toString());
329    }
330}