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