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.server.authorization.dseecompat.Aci.*;
020
021import java.util.LinkedList;
022import java.util.List;
023import java.util.Set;
024
025import org.forgerock.opendj.ldap.ByteString;
026import org.forgerock.opendj.ldap.schema.AttributeType;
027import org.forgerock.opendj.ldap.schema.CoreSchema;
028import org.opends.server.core.DirectoryServer;
029import org.opends.server.types.Attribute;
030import org.opends.server.types.Attributes;
031import org.opends.server.types.Entry;
032
033/** This class implements the dseecompat geteffectiverights evaluation. */
034public class AciEffectiveRights {
035  /** Value used when a aclRights attribute was seen in the search operation attribute set. */
036  private static final int ACL_RIGHTS = 0x001;
037
038  /** Value used when a aclRightsInfo attribute was seen in the search operation attribute set. */
039  private static final int ACL_RIGHTS_INFO = 0x002;
040
041  /**
042   * Value used when an ACI has a targattrfilters keyword match and the result
043   * of the access check was a deny.
044   */
045  private static final int ACL_TARGATTR_DENY_MATCH = 0x004;
046
047  /**
048   * Value used when an ACI has a targattrfilters keyword match and the result
049   * of the access check was an allow.
050   */
051  private static final int ACL_TARGATTR_ALLOW_MATCH = 0x008;
052
053  /**
054   * String used to build attribute type name when an aclRights result needs to
055   * be added to the return entry.
056   */
057  private static final String aclRightsAttrStr = "aclRights";
058
059  /**
060   * String used to build attribute type name when an AclRightsInfo result needs
061   * to be added to the return entry.
062   */
063  private static final String aclRightsInfoAttrStr = "aclRightsInfo";
064
065  /**
066   * String used to build attribute type name when an entryLevel rights
067   * attribute type name needs to be added to the return entry.
068   */
069  private static final String entryLevelStr = "entryLevel";
070
071  /**
072   * String used to build attribute type name when an attributeLevel rights
073   * attribute type name needs to be added to the return entry.
074   */
075  private static final String attributeLevelStr = "attributeLevel";
076
077  /**
078   * The string that is used as the attribute type name when an aclRights
079   * entryLevel evaluation needs to be added to the return entry.
080   */
081  private static final String aclRightsEntryLevelStr=
082                                     aclRightsAttrStr + ";" + entryLevelStr;
083
084  /**
085   * The string that is used as the attribute type name when an aclRights
086   * attribute level evaluation needs to be added to the return entry. This
087   * string has the attribute type name used in the evaluation appended to it to
088   * form the final attribute type name.
089   */
090  private static final String aclRightsAttributeLevelStr=
091        aclRightsAttrStr +  ";" + attributeLevelStr;
092
093  /**
094   * The string used to build attribute type name when an attribute level
095   * aclRightsInfo attribute needs to be added to the return entry. This string
096   * has the attribute type name used in the evaluation appended to it to form
097   * the final attribute type name.
098   */
099  private static final String aclRightsInfoAttrLogsStr =
100                      aclRightsInfoAttrStr + ";logs;attributeLevel";
101
102  /**
103   * The string used to build attribute type name when an entryLevel
104   * aclRightsInfo attribute needs to be added to the return entry.
105   */
106  private static final String aclRightsInfoEntryLogsStr =
107                      aclRightsInfoAttrStr + ";logs;entryLevel";
108
109  /**
110   * Attribute type used in access evaluation to see if the geteffectiverights
111   * related to the "aclRights" attribute can be performed.
112   */
113  private static AttributeType aclRights;
114
115  /**
116   * Attribute type used in access evaluation to see if the geteffectiverights
117   * related to the "aclRightsInfo" attribute can be performed.
118   */
119  private static AttributeType aclRightsInfo;
120
121  /** Attribute type used in the geteffectiverights selfwrite evaluation. */
122  private static AttributeType dnAttributeType;
123
124  /** The distinguishedName string. */
125  private static final String dnAttrStr = "distinguishedname";
126
127  /** String used to fill in the summary status field when access was allowed. */
128  private static final String ALLOWED = "access allowed";
129  /** String used to fill in the summary status field when access was not allowed. */
130  private static final String NOT_ALLOWED = "access not allowed";
131
132  /** Evaluated as anonymous user. Used to fill in summary field. */
133  private static final String anonymous = "anonymous";
134
135  /** Format used to build the summary string. */
136  private static final String summaryFormatStr =
137        "acl_summary(%s): %s(%s) on entry/attr(%s, %s) to (%s)" +
138        " (not proxied) ( reason: %s %s)";
139
140  /**
141   * Strings below represent access denied or allowed evaluation reasons. Used
142   * to fill in the summary status field. Access evaluated an allow ACI.
143   */
144  private static final String EVALUATED_ALLOW = "evaluated allow";
145  /** Access evaluated a deny ACI. */
146  private static final String EVALUATED_DENY = "evaluated deny";
147  /** Access evaluated deny because there were no allow ACIs. */
148  private static final String NO_ALLOWS = "no acis matched the resource";
149  /** Access evaluated deny because no allow or deny ACIs evaluated. */
150  private static final String NO_ALLOWS_MATCHED = "no acis matched the subject";
151  /** Access evaluated allow because the clientDN has bypass-acl privileges. */
152  private static final String SKIP_ACI = "user has bypass-acl privileges";
153
154  //TODO add support for the modify-acl privilege?
155
156  /**
157   * Attempts to add the geteffectiverights asked for in the search to the entry
158   * being returned. The two geteffectiverights attributes that can be requested
159   * are: aclRights and aclRightsInfo. The aclRightsInfo attribute will return
160   * a summary string describing in human readable form, a summary of each
161   * requested evaluation result. Here is a sample aclRightsInfo summary:
162   *
163   * acl_summary(main): access_not_allowed(proxy) on
164   * entry/attr(uid=proxieduser,ou=acis,dc=example,dc=com, NULL) to
165   * (uid=superuser,ou=acis,dc=example,dc=com) (not proxied)
166   * (reason: no acis matched the resource )
167   *
168   * The aclRights attribute will return a simple
169   * string with the following format:
170   *
171   *        add:0,delete:0,read:1,write:?,proxy:0
172   *
173   * A 0 represents access denied, 1 access allowed and ? that evaluation
174   * depends on a value of an attribute (targattrfilter keyword present in ACI).
175   *
176   * There are two levels of rights information:
177   *
178   *  1. entryLevel - entry level rights information
179   *  2. attributeLevel - attribute level rights information
180   *
181   * The attribute type names are built up using subtypes:
182   *
183   *    aclRights;entryLevel - aclRights entry level presentation
184   *    aclRightsInfo;log;entryLevel;{right} - aclRightsInfo entry level
185   *        presentation for each type of right (proxy, read, write, add,
186   *        delete).
187   *    aclRights;attributeLevel;{attributeType name} - aclRights attribute
188   *        level presentation for each attribute type requested.
189   *    aclRights;attributeLevel;logs;{right};{attributeType name}
190   *        - aclRightsInfo  attribute level presentation for each attribute
191   *          type requested.
192   *
193   * @param handler  The ACI handler to use in the evaluation.
194   * @param searchAttributes  The attributes requested in the search.
195   * @param container  The LDAP operation container to use in the evaluations.
196   * @param e The entry to add the rights attributes to.
197   * @param skipCheck  True if ACI evaluation was skipped because bypass-acl
198   *                   privilege was found.
199   */
200  public static void addRightsToEntry(AciHandler handler,
201      Set<String> searchAttributes,
202      AciLDAPOperationContainer container, final Entry e,
203      boolean skipCheck)
204  {
205    if (aclRights == null)
206    {
207      aclRights = DirectoryServer.getSchema().getAttributeType(aclRightsAttrStr);
208    }
209    if (aclRightsInfo == null)
210    {
211      aclRightsInfo = DirectoryServer.getSchema().getAttributeType(aclRightsInfoAttrStr);
212    }
213    if (dnAttributeType == null)
214    {
215      dnAttributeType = DirectoryServer.getSchema().getAttributeType(dnAttrStr);
216    }
217
218    // Check if the attributes aclRights and aclRightsInfo were requested and
219    // add attributes less those two attributes to a new list of attribute
220    // types.
221    List<AttributeType> nonRightsAttrs = new LinkedList<>();
222    int attrMask = ACI_NULL;
223    for (String a : searchAttributes)
224    {
225      if (aclRightsAttrStr.equalsIgnoreCase(a))
226      {
227        attrMask |= ACL_RIGHTS;
228      }
229      else if (aclRightsInfoAttrStr.equalsIgnoreCase(a))
230      {
231        attrMask |= ACL_RIGHTS_INFO;
232      }
233      else
234      {
235        // Check for shorthands for user attributes "*" or operational "+".
236        if ("*".equals(a))
237        {
238          nonRightsAttrs.add(CoreSchema.getObjectClassAttributeType());
239          nonRightsAttrs.addAll(e.getUserAttributes().keySet());
240        }
241        else if ("+".equals(a))
242        {
243          nonRightsAttrs.addAll(e.getOperationalAttributes().keySet());
244        }
245        else
246        {
247          nonRightsAttrs.add(DirectoryServer.getSchema().getAttributeType(a));
248        }
249      }
250    }
251
252    // If the special geteffectiverights attributes were not found or
253    // the user does not have both bypass-acl privs and is not allowed to
254    // perform rights evaluation -- return the entry unchanged.
255    if (attrMask == ACI_NULL
256        || (!skipCheck && !rightsAccessAllowed(container, handler, attrMask)))
257    {
258      return;
259    }
260
261    // From here on out, geteffectiverights evaluation is being performed and
262    // the container will be manipulated. First set the flag that
263    // geteffectiverights evaluation's underway and to use the authZid for
264    // authorizationDN (they might be the same).
265    container.setGetEffectiveRightsEval();
266    container.useAuthzid(true);
267
268    // If no attributes were requested return only entryLevel rights, else
269    // return attributeLevel rights and entryLevel rights. Always try and
270    // return the specific attribute rights if they exist.
271    if (!nonRightsAttrs.isEmpty())
272    {
273      addAttributeLevelRights(container, handler, attrMask, e, nonRightsAttrs,
274          skipCheck, false);
275    }
276    addAttributeLevelRights(container, handler, attrMask, e, container
277        .getSpecificAttributes(), skipCheck, true);
278    addEntryLevelRights(container, handler, attrMask, e, skipCheck);
279  }
280
281  /**
282   * Perform the attributeLevel rights evaluation on a list of specified
283   * attribute types. Each attribute has an access check done for the following
284   * rights: search, read, compare, add, delete, proxy, selfwrite_add,
285   * selfwrite_delete and write. The special rights, selfwrite_add and
286   * selfwrite_delete, use the authZid as the attribute value to evaluate
287   * against the attribute type being evaluated. The selfwrite_add performs the
288   * access check using the ACI_WRITE_ADD right and selfwrite_delete uses
289   * ACI_WRITE_ADD right. The write right is made complicated by the
290   * targattrfilters keyword, which might depend on an unknown value of an
291   * attribute type. For this case a dummy attribute value is used to try and
292   * determine if a "?" needs to be placed in the rights string. The special
293   * flag ACI_SKIP_PROXY_CHECK is always set, so that proxy evaluation is
294   * bypassed in the Aci Handler's accessAllowed method.
295   *
296   * @param container
297   *          The LDAP operation container to use in the evaluations.
298   * @param handler
299   *          The Aci Handler to use in the access evaluations.
300   * @param mask
301   *          Mask specifying what rights attribute processing to perform
302   *          (aclRights or aclRightsInfo or both).
303   * @param retEntry
304   *          The entry to return.
305   * @param attrList
306   *          The list of attribute types to iterate over.
307   * @param skipCheck
308   *          True if ACI evaluation was skipped because bypass-acl privilege
309   *          was found.
310   * @param specificAttr
311   *          True if this evaluation is result of specific attributes sent in
312   *          the request.
313   */
314  private static void addAttributeLevelRights(
315      AciLDAPOperationContainer container, AciHandler handler, int mask,
316      final Entry retEntry, List<AttributeType> attrList,
317      boolean skipCheck, boolean specificAttr)
318  {
319    if (attrList == null)
320    {
321      return;
322    }
323
324    for(AttributeType a : attrList) {
325      StringBuilder evalInfo=new StringBuilder();
326      container.setCurrentAttributeType(a);
327      container.setCurrentAttributeValue(null);
328      //Perform search check and append results.
329      container.setRights(ACI_SEARCH | ACI_SKIP_PROXY_CHECK);
330      evalInfo.append(rightsString(container, handler, skipCheck, "search"));
331      addAttrLevelRightsInfo(container, mask, a, retEntry, "search");
332      evalInfo.append(',');
333      //Perform read check and append results.
334      container.setRights(ACI_READ | ACI_SKIP_PROXY_CHECK);
335      evalInfo.append(rightsString(container, handler, skipCheck, "read"));
336      addAttrLevelRightsInfo(container, mask, a, retEntry, "read");
337      evalInfo.append(',');
338      //Perform compare and append results.
339      container.setRights(ACI_COMPARE | ACI_SKIP_PROXY_CHECK);
340      evalInfo.append(rightsString(container, handler, skipCheck, "compare"));
341      addAttrLevelRightsInfo(container, mask, a, retEntry, "compare");
342      evalInfo.append(',');
343      //Write right is more complicated. Create a dummy value and set that as
344      //the attribute's value. Call the special writeRightsString method, rather
345      //than rightsString.
346      ByteString val= ByteString.valueOfUtf8("dum###Val");
347      container.setCurrentAttributeValue(val);
348      evalInfo.append(attributeLevelWriteRights(container, handler, skipCheck));
349      addAttrLevelRightsInfo(container, mask, a, retEntry, "write");
350      evalInfo.append(',');
351      //Perform both selfwrite_add and selfwrite_delete and append results.
352      ByteString val1 = ByteString.valueOfUtf8(container.getClientDN().toString());
353      if(!specificAttr)
354      {
355        container.setCurrentAttributeType(dnAttributeType);
356      }
357      container.setCurrentAttributeValue(val1);
358      container.setRights(ACI_WRITE_ADD | ACI_SKIP_PROXY_CHECK);
359      evalInfo.append(rightsString(container, handler, skipCheck,
360                      "selfwrite_add"));
361      addAttrLevelRightsInfo(container, mask, a, retEntry, "selfwrite_add");
362      evalInfo.append(',');
363      container.setRights(ACI_WRITE_DELETE | ACI_SKIP_PROXY_CHECK);
364      evalInfo.append(rightsString(container, handler, skipCheck,
365                       "selfwrite_delete"));
366      addAttrLevelRightsInfo(container, mask, a, retEntry, "selfwrite_delete");
367      evalInfo.append(',');
368      container.setCurrentAttributeType(a);
369      container.setCurrentAttributeValue(null);
370                container.setRights(ACI_PROXY | ACI_SKIP_PROXY_CHECK);
371      evalInfo.append(rightsString(container, handler, skipCheck, "proxy"));
372      addAttrLevelRightsInfo(container, mask, a, retEntry, "proxy");
373      //It is possible that only the aclRightsInfo attribute type was requested.
374      // Only add the aclRights information if the aclRights attribute type was seen.
375      if(hasAttrMask(mask, ACL_RIGHTS))  {
376        String typeStr = aclRightsAttributeLevelStr + ";" + a.getNameOrOID();
377        AttributeType attributeType = DirectoryServer.getSchema().getAttributeType(typeStr);
378        Attribute attr = Attributes.create(attributeType, evalInfo.toString());
379        //It is possible that the user might have specified the same attributes
380        //in both the search and the specific attribute part of the control.
381        //Only try to add the attribute type if it already hasn't been added.
382        if(!retEntry.hasAttribute(attributeType))
383        {
384          retEntry.addAttribute(attr,null);
385        }
386      }
387    }
388    container.setCurrentAttributeValue(null);
389    container.setCurrentAttributeType(null);
390  }
391
392  /**
393   * Perform the attributeLevel write rights evaluation. The issue here is that
394   * an ACI could contain a targattrfilters keyword that matches the attribute
395   * being evaluated. There is no way of knowing if the filter part of the
396   * targattrfilter would be successful or not. So if the ACI that allowed
397   * access, has an targattrfilter keyword, a "?" is used as the result of the
398   * write (depends on attribute value). If the allow ACI doesn't contain a
399   * targattrfilters keyword than a "1" is added. If the ACI denies then a "0"
400   * is added. If the skipCheck flag is true, then a 1 is used for the write
401   * access, since the client DN has bypass privs.
402   *
403   * @param container
404   *          The LDAP operation container to use in the evaluations.
405   * @param handler
406   *          The Aci Handler to use in the access evaluations.
407   * @param skipCheck
408   *          True if ACI evaluation was skipped because bypass-acl privilege
409   *          was found.
410   * @return A string representing the rights information.
411   */
412  private static String attributeLevelWriteRights(
413      AciLDAPOperationContainer container, AciHandler handler,
414      boolean skipCheck)
415  {
416    StringBuilder resString=new  StringBuilder();
417    //If the user has bypass-acl privs and the authzid is equal to the
418    //authorization dn, create a right string with a '1' and a valid
419    //summary. If the user has bypass-acl privs and is querying for
420    //another authzid or they don't have privs  -- fall through.
421    if(skipCheck && container.isAuthzidAuthorizationDN()) {
422      resString.append("write").append(":1");
423      container.setEvaluationResult(EnumEvalReason.SKIP_ACI, null);
424      container.setEvalSummary(createSummary(container, true));
425    } else {
426      // Reset everything.
427      container.resetEffectiveRightsParams();
428      //Reset name.
429      container.setTargAttrFiltersAciName(null);
430      container.setRights(ACI_WRITE_ADD | ACI_SKIP_PROXY_CHECK);
431      final boolean addRet = handler.accessAllowed(container)
432              && container.getTargAttrFiltersAciName() == null;
433      container.setRights(ACI_WRITE_DELETE | ACI_SKIP_PROXY_CHECK);
434      final boolean delRet = handler.accessAllowed(container)
435              && container.getTargAttrFiltersAciName() == null;
436      //If both booleans are true, then access was allowed by ACIs that did
437      //not contain targattrfilters.
438      if(addRet && delRet) {
439        resString.append("write").append(":1");
440      } else {
441        //If there is an ACI name then an ACI with a targattrfilters allowed,
442        //access. A '?' is needed because that evaluation really depends on an
443        //unknown attribute value, not the dummy value. If there is no ACI
444        //then one of the above access checks failed and a '0' is needed.
445        if(container.getTargAttrFiltersAciName() != null) {
446          resString.append("write").append(":?");
447        } else {
448          resString.append("write").append(":0");
449        }
450      }
451    }
452    return resString.toString();
453  }
454
455
456
457  /**
458   * Perform entryLevel rights evaluation. The rights string is added to the
459   * entry if the aclRights attribute was seen in the search's requested
460   * attribute set.
461   *
462   * @param container
463   *          The LDAP operation container to use in the evaluations.
464   * @param handler
465   *          The Aci Handler to use in the access evaluations.
466   * @param mask
467   *          Mask specifying what rights attribute processing to perform
468   *          (aclRights or aclRightsInfo or both).
469   * @param retEntry
470   *          The entry to return.
471   * @param skipCheck
472   *          True if ACI evaluation was skipped because bypass-acl privilege
473   *          was found.
474   */
475  private static void addEntryLevelRights(AciLDAPOperationContainer container,
476      AciHandler handler, int mask, final Entry retEntry,
477      boolean skipCheck)
478  {
479    //Perform access evaluations for rights: add, delete, read, write, proxy.
480    StringBuilder evalInfo=new StringBuilder();
481    container.setCurrentAttributeType(null);
482    container.setRights(ACI_ADD | ACI_SKIP_PROXY_CHECK);
483    evalInfo.append(rightsString(container, handler, skipCheck, "add"));
484    addEntryLevelRightsInfo(container, mask, retEntry, "add");
485    evalInfo.append(',');
486    container.setCurrentAttributeType(null);
487    container.setRights(ACI_DELETE | ACI_SKIP_PROXY_CHECK);
488    evalInfo.append(rightsString(container, handler, skipCheck, "delete"));
489    addEntryLevelRightsInfo(container, mask, retEntry, "delete");
490    evalInfo.append(',');
491    //The read right needs the entry with the full set of attributes. This was
492    //saved in the Aci Handlers maysend method.
493    container.setCurrentAttributeType(null);
494    container.setRights(ACI_READ | ACI_SKIP_PROXY_CHECK);
495    evalInfo.append(rightsString(container, handler, skipCheck, "read"));
496    addEntryLevelRightsInfo(container, mask, retEntry, "read");
497    evalInfo.append(',');
498    //Switch back to the entry from the Aci Handler's filterentry method.
499    container.setCurrentAttributeType(null);
500    container.setRights(ACI_WRITE| ACI_SKIP_PROXY_CHECK);
501    evalInfo.append(rightsString(container, handler, skipCheck, "write"));
502    addEntryLevelRightsInfo(container, mask, retEntry, "write");
503    evalInfo.append(',');
504    container.setCurrentAttributeType(null);
505    container.setRights(ACI_PROXY| ACI_SKIP_PROXY_CHECK);
506    evalInfo.append(rightsString(container, handler, skipCheck, "proxy"));
507    addEntryLevelRightsInfo(container, mask, retEntry, "proxy");
508    if(hasAttrMask(mask, ACL_RIGHTS)) {
509      Attribute attr = Attributes.create(aclRightsEntryLevelStr, evalInfo.toString());
510      retEntry.addAttribute(attr,null);
511    }
512  }
513
514  /**
515   * Create the rights for aclRights attributeLevel or entryLevel rights
516   * evaluation. The only right needing special treatment is the read right
517   * with no current attribute type set in the container. For that case the
518   * accessAllowedEntry method is used instead of the accessAllowed method.
519   *
520   * @param container The LDAP operation container to use in the evaluations.
521   * @param handler The Aci Handler to use in the access evaluations.
522   * @param skipCheck True if ACI evaluation was skipped because bypass-acl
523   *                  privilege was found.
524   * @param rightStr String used representation of the right we are evaluating.
525   * @return  A string representing the aclRights for the current right and
526   * attribute type/value combinations.
527   */
528  private static
529  String rightsString(AciLDAPOperationContainer container,
530                                            AciHandler handler,
531                                            boolean skipCheck, String rightStr){
532    StringBuilder resString=new  StringBuilder();
533    container.resetEffectiveRightsParams();
534    //If the user has bypass-acl privs and the authzid is equal to the
535    //authorization dn, create a right string with a '1' and a valid
536    //summary. If the user has bypass-acl privs and is querying for
537    //another authzid or they don't have privs  -- fall through.
538    if(skipCheck && container.isAuthzidAuthorizationDN()) {
539      resString.append(rightStr).append(":1");
540      container.setEvaluationResult(EnumEvalReason.SKIP_ACI, null);
541      container.setEvalSummary(createSummary(container, true));
542    } else {
543      boolean ret;
544      //Check if read right check, if so do accessAllowedEntry.
545      if(container.hasRights(ACI_READ) &&
546         container.getCurrentAttributeType() == null)
547      {
548        ret=handler.accessAllowedEntry(container);
549      }
550      else
551      {
552        ret=handler.accessAllowed(container);
553      }
554
555      resString.append(rightStr).append(ret ? ":1" : ":0");
556    }
557    return resString.toString();
558  }
559
560  /**
561   * Check that access is allowed on the aclRights and/or aclRightsInfo
562   * attribute types.
563   *
564   * @param container The LDAP operation container to use in the evaluations.
565   * @param handler   The Aci Handler to use in the access evaluations.
566   * @param mask Mask specifying what rights attribute processing to perform
567   *              (aclRights or aclRightsInfo or both).
568   * @return True if access to the geteffectiverights attribute types are
569   *         allowed.
570   */
571  private static
572  boolean rightsAccessAllowed(AciLDAPOperationContainer container,
573                              AciHandler handler, int mask) {
574    boolean retRight=true, retInfo=true;
575    if(hasAttrMask(mask, ACL_RIGHTS)) {
576        container.setCurrentAttributeType(aclRights);
577        container.setRights(ACI_READ | ACI_SKIP_PROXY_CHECK);
578        retRight=handler.accessAllowed(container);
579    }
580    if(hasAttrMask(mask, ACL_RIGHTS_INFO)) {
581        container.setCurrentAttributeType(aclRightsInfo);
582        container.setRights(ACI_READ | ACI_SKIP_PROXY_CHECK);
583        retInfo=handler.accessAllowed(container);
584    }
585    return retRight && retInfo;
586  }
587
588  /**
589   * Add aclRightsInfo attributeLevel information to the entry. This is the
590   * summary string built from the last access check.
591   *
592   * @param container  The LDAP operation container to use in the evaluations.
593   * @param mask  Mask specifying what rights attribute processing to perform
594   *              (aclRights or aclRightsInfo or both).
595   * @param aType The attribute type to use in building the attribute type name.
596   * @param retEntry The entry to add the rights information to.
597   * @param rightStr The string representation of the rights evaluated.
598   */
599  private static
600  void addAttrLevelRightsInfo(AciLDAPOperationContainer container, int mask,
601                     AttributeType aType, Entry retEntry,
602                     String rightStr) {
603    //Check if the aclRightsInfo attribute was requested.
604    if(hasAttrMask(mask,ACL_RIGHTS_INFO)) {
605      //Build the attribute type.
606      String typeStr = aclRightsInfoAttrLogsStr + ";" + rightStr + ";" + aType.getNameOrOID();
607      AttributeType attributeType = DirectoryServer.getSchema().getAttributeType(typeStr);
608      Attribute attr = Attributes.create(attributeType, container.getEvalSummary());
609      // The attribute type might have already been added, probably
610      // not but it is possible.
611      if(!retEntry.hasAttribute(attributeType))
612      {
613        retEntry.addAttribute(attr,null);
614      }
615    }
616  }
617
618  /**
619   * Add aclRightsInfo entryLevel rights to the entry to be returned. This is
620   * the summary string built from the last access check.
621   *
622   * @param container   The LDAP operation container to use in the evaluations.
623   * @param mask Mask specifying what rights attribute processing to perform
624   *              (aclRights or aclRightsInfo or both).
625   * @param retEntry  The entry to add the rights information to.
626   * @param rightStr The string representation of the rights evaluated.
627   */
628  private static
629   void addEntryLevelRightsInfo(AciLDAPOperationContainer container, int mask,
630                       Entry retEntry,
631                      String rightStr) {
632     //Check if the aclRightsInfo attribute was requested.
633     if(hasAttrMask(mask,ACL_RIGHTS_INFO)) {
634      String typeStr = aclRightsInfoEntryLogsStr + ";" + rightStr;
635      Attribute attr = Attributes.create(typeStr, container.getEvalSummary());
636       retEntry.addAttribute(attr,null);
637     }
638   }
639
640  /**
641   * Check if the provided mask has a specific rights attr value.
642   *
643   * @param mask The mask with the attribute flags.
644   * @param rightsAttr The rights attr value to check for.
645   * @return True if the mask contains the rights attr value.
646   */
647  private static boolean hasAttrMask(int mask, int rightsAttr) {
648        return (mask & rightsAttr) != 0;
649  }
650
651  /**
652   * Create the summary string used in the aclRightsInfo log string.
653   *
654   * @param evalCtx The evaluation context to gather information from.
655   * @param evalRet The value returned from the access evaluation.
656   * @return A summary of the ACI evaluation
657   */
658  public static String createSummary(AciEvalContext evalCtx, boolean evalRet)
659  {
660    String srcStr = "main";
661    String accessStatus = evalRet ? ALLOWED : NOT_ALLOWED;
662
663    //Try and determine what reason string to use.
664    String accessReason = getEvalReason(evalCtx.getEvalReason());
665    StringBuilder decideAci =
666        getDecidingAci(evalCtx.getEvalReason(), evalCtx.getDecidingAciName());
667
668    //Only manipulate the evaluation context's targattrfilters ACI name
669    //if not a selfwrite evaluation and the context's targattrfilter match
670    //hashtable is not empty.
671    if(!evalCtx.isTargAttrFilterMatchAciEmpty() &&
672            !evalCtx.hasRights(ACI_SELF)) {
673      //If the allow list was empty then access is '0'.
674      if(evalCtx.getAllowList().isEmpty()) {
675        evalCtx.setTargAttrFiltersAciName(null);
676      } else if(evalRet) {
677        //The evaluation returned true, clear the evaluation context's
678        //targattrfilters ACI name only if a deny targattrfilters ACI
679        //was not seen. It could remove the allow.
680        if(!evalCtx.hasTargAttrFiltersMatchOp(ACL_TARGATTR_DENY_MATCH))
681        {
682          evalCtx.setTargAttrFiltersAciName(null);
683        }
684      } else {
685        //The evaluation returned false. If the reason was an
686        //explicit deny evaluation by a non-targattrfilters ACI, clear
687        //the evaluation context's targattrfilters ACI name since targattrfilter
688        //evaluation is pretty much ignored during geteffectiverights eval.
689        //Else, it was a non-explicit deny, if there is not a targattrfilters
690        //ACI that might have granted access the deny stands, else there is
691        //a targattrfilters ACI that might grant access.
692        if(evalCtx.getEvalReason() == EnumEvalReason.EVALUATED_DENY_ACI)
693        {
694          evalCtx.setTargAttrFiltersAciName(null);
695        }
696        else if(!evalCtx.hasTargAttrFiltersMatchOp(ACL_TARGATTR_ALLOW_MATCH))
697        {
698          evalCtx.setTargAttrFiltersAciName(null);
699        }
700      }
701    }
702    //Actually build the string.
703    String user=anonymous;
704    if(!evalCtx.getClientDN().isRootDN())
705    {
706      user=evalCtx.getClientDN().toString();
707    }
708    String right=evalCtx.rightToString();
709    AttributeType aType=evalCtx.getCurrentAttributeType();
710    String attrStr="NULL";
711    if(aType != null)
712    {
713      attrStr = aType.getNameOrOID();
714    }
715    if(evalCtx.getTargAttrFiltersAciName() != null)
716    {
717      decideAci.append(", access depends on attr value");
718    }
719    return String.format(summaryFormatStr, srcStr, accessStatus,
720                         right,evalCtx.getResourceDN().toString(),attrStr, user,
721                            accessReason, decideAci.toString());
722  }
723
724  private static String getEvalReason(EnumEvalReason evalReason)
725  {
726    if (evalReason == EnumEvalReason.EVALUATED_ALLOW_ACI)
727    {
728      return EVALUATED_ALLOW;
729    }
730    else if (evalReason == EnumEvalReason.EVALUATED_DENY_ACI)
731    {
732      return EVALUATED_DENY;
733    }
734    else if (evalReason == EnumEvalReason.NO_ALLOW_ACIS)
735    {
736      return NO_ALLOWS;
737    }
738    else if (evalReason == EnumEvalReason.NO_MATCHED_ALLOWS_ACIS)
739    {
740      return NO_ALLOWS_MATCHED;
741    }
742    else if (evalReason == EnumEvalReason.SKIP_ACI)
743    {
744      return SKIP_ACI;
745    }
746    return "";
747  }
748
749  private static StringBuilder getDecidingAci(EnumEvalReason evalReason,
750      String decidingAciName)
751  {
752    StringBuilder decideAci = new StringBuilder();
753    if (evalReason == EnumEvalReason.EVALUATED_ALLOW_ACI)
754    {
755      decideAci.append(", deciding_aci: ").append(decidingAciName);
756    }
757    else if (evalReason == EnumEvalReason.EVALUATED_DENY_ACI)
758    {
759      decideAci.append(", deciding_aci: ").append(decidingAciName);
760    }
761    return decideAci;
762  }
763
764  /**
765   * If the specified ACI is in the targattrfilters hashtable contained in the
766   * evaluation context, set the  evaluation context's targattrfilters match
767   * variable to either ACL_TARGATTR_DENY_MATCH or ACL_TARGATTR_ALLOW_MATCH
768   * depending on the value of the variable denyAci.
769   *
770   * @param evalCtx The evaluation context to evaluate and save information to.
771   * @param aci The ACI to match.
772   * @param denyAci True if the evaluation was a allow, false if the
773   *                evaluation was an deny or the ACI is not in the table.
774   * @return  True if the ACI was found in the hashtable.
775   */
776  public static
777  boolean  setTargAttrAci(AciEvalContext evalCtx, Aci aci, boolean denyAci) {
778    if(evalCtx.hasTargAttrFiltersMatchAci(aci)) {
779      int flag = denyAci ? ACL_TARGATTR_DENY_MATCH : ACL_TARGATTR_ALLOW_MATCH;
780      evalCtx.setTargAttrFiltersMatchOp(flag);
781      return true;
782    }
783    return false;
784  }
785
786  /**
787   * Finalizes static variables on shutdown so that we release the memory
788   * associated with them (for the unit tests) and get fresh copies if we're
789   * doing an in-core restart.
790   */
791  public static void finalizeOnShutdown() {
792    AciEffectiveRights.aclRights = null;
793    AciEffectiveRights.aclRightsInfo = null;
794    AciEffectiveRights.dnAttributeType = null;
795  }
796}