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-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2011-2016 ForgeRock AS.
016 * Portions Copyright 2013 Manuel Gaupp
017 */
018package org.opends.server.authorization.dseecompat;
019
020import java.util.LinkedList;
021import java.util.List;
022import java.util.SortedSet;
023import java.util.TreeSet;
024
025import org.forgerock.i18n.LocalizedIllegalArgumentException;
026import org.forgerock.i18n.slf4j.LocalizedLogger;
027import org.forgerock.opendj.config.server.ConfigException;
028import org.forgerock.opendj.ldap.AVA;
029import org.forgerock.opendj.ldap.AttributeDescription;
030import org.forgerock.opendj.ldap.ByteString;
031import org.forgerock.opendj.ldap.DN;
032import org.forgerock.opendj.ldap.ModificationType;
033import org.forgerock.opendj.ldap.RDN;
034import org.forgerock.opendj.ldap.ResultCode;
035import org.forgerock.opendj.ldap.schema.AttributeType;
036import org.forgerock.opendj.server.config.server.DseeCompatAccessControlHandlerCfg;
037import org.opends.server.api.AccessControlHandler;
038import org.opends.server.api.ClientConnection;
039import org.opends.server.backends.pluggable.SuffixContainer;
040import org.opends.server.controls.GetEffectiveRightsRequestControl;
041import org.opends.server.core.BindOperation;
042import org.opends.server.core.DirectoryServer;
043import org.opends.server.core.ExtendedOperation;
044import org.opends.server.core.ModifyDNOperation;
045import org.opends.server.core.SearchOperation;
046import org.opends.server.protocols.ldap.LDAPControl;
047import org.opends.server.types.Attribute;
048import org.opends.server.types.AttributeBuilder;
049import org.opends.server.types.AuthenticationInfo;
050import org.opends.server.types.Control;
051import org.opends.server.types.DirectoryException;
052import org.opends.server.types.Entry;
053import org.opends.server.types.InitializationException;
054import org.opends.server.types.Modification;
055import org.opends.server.types.Operation;
056import org.opends.server.types.Privilege;
057import org.opends.server.types.SearchFilter;
058import org.opends.server.types.SearchResultEntry;
059import org.opends.server.types.SearchResultReference;
060import org.opends.server.workflowelement.localbackend.LocalBackendAddOperation;
061import org.opends.server.workflowelement.localbackend.LocalBackendCompareOperation;
062import org.opends.server.workflowelement.localbackend.LocalBackendDeleteOperation;
063import org.opends.server.workflowelement.localbackend.LocalBackendModifyOperation;
064
065import static org.opends.messages.AccessControlMessages.*;
066import static org.opends.server.authorization.dseecompat.Aci.*;
067import static org.opends.server.authorization.dseecompat.EnumEvalReason.*;
068import static org.opends.server.config.ConfigConstants.*;
069import static org.opends.server.core.DirectoryServer.*;
070import static org.opends.server.schema.SchemaConstants.*;
071import static org.opends.server.util.ServerConstants.*;
072import static org.opends.server.util.StaticUtils.*;
073
074/** The AciHandler class performs the main processing for the dseecompat package. */
075public final class AciHandler extends
076    AccessControlHandler<DseeCompatAccessControlHandlerCfg>
077{
078  /**
079   * String used to indicate that the evaluating ACI had a all
080   * operational attributes targetattr match (targetattr="+").
081   */
082  static final String ALL_OP_ATTRS_MATCHED = "allOpAttrsMatched";
083
084  /**
085   * String used to indicate that the evaluating ACI had a all user
086   * attributes targetattr match (targetattr="*").
087   */
088  static final String ALL_USER_ATTRS_MATCHED = "allUserAttrsMatched";
089
090  /**
091   * String used to save the original authorization entry in an
092   * operation attachment if a proxied authorization control was seen.
093   */
094  static final String ORIG_AUTH_ENTRY = "origAuthorizationEntry";
095
096  /** Attribute type corresponding to "aci" attribute. */
097  static AttributeType aciType;
098
099  /** Attribute type corresponding to global "ds-cfg-global-aci" attribute. */
100  static AttributeType globalAciType;
101
102  /** Attribute type corresponding to "debugsearchindex" attribute. */
103  private static AttributeType debugSearchIndex;
104
105  /** DN corresponding to "debugsearchindex" attribute type. */
106  private static DN debugSearchIndexDN;
107
108  /**
109   * Attribute type corresponding to the "ref" attribute type. Used in
110   * the search reference access check.
111   */
112  private static AttributeType refAttrType;
113  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
114
115  static
116  {
117    initStatics();
118  }
119
120  /**
121   * We initialize these for each new AciHandler so that we can clear out the
122   * stale references that can occur during an in-core restart.
123   */
124  private static void initStatics()
125  {
126    aciType = getSchema().getAttributeType("aci");
127    globalAciType = getSchema().getAttributeType(ATTR_AUTHZ_GLOBAL_ACI);
128    debugSearchIndex = getSchema().getAttributeType(SuffixContainer.ATTR_DEBUG_SEARCH_INDEX);
129    refAttrType = getSchema().getAttributeType(ATTR_REFERRAL_URL);
130
131    try
132    {
133      debugSearchIndexDN = DN.valueOf("cn=debugsearch");
134    }
135    catch (LocalizedIllegalArgumentException unexpected)
136    {
137      // Should never happen.
138    }
139  }
140
141  /** The list that holds that ACIs keyed by the DN of the entry holding the ACI. */
142  private AciList aciList;
143
144  /**
145   * The listener that handles ACI changes caused by LDAP operations,
146   * ACI decode failure alert logging and backend initialization ACI list adjustment.
147   */
148  private AciListenerManager aciListenerMgr;
149
150  /** Creates a new DSEE-compatible access control handler. */
151  public AciHandler()
152  {
153    // No implementation required. All initialization should be done in
154    // the intializeAccessControlHandler method.
155  }
156
157  @Override
158  public void filterEntry(Operation operation,
159      SearchResultEntry unfilteredEntry, SearchResultEntry filteredEntry)
160  {
161    AciLDAPOperationContainer container =
162        new AciLDAPOperationContainer(operation, ACI_READ, unfilteredEntry);
163
164    // Proxy access check has already been done for this entry in the
165    // maySend method, set the seen flag to true to bypass any proxy check.
166    container.setSeenEntry(true);
167
168    boolean skipCheck = skipAccessCheck(operation);
169    if (!skipCheck)
170    {
171      filterEntry(container, filteredEntry);
172    }
173
174    if (container.hasGetEffectiveRightsControl())
175    {
176      AciEffectiveRights.addRightsToEntry(this,
177          ((SearchOperation) operation).getAttributes(), container,
178          filteredEntry, skipCheck);
179    }
180  }
181
182  @Override
183  public void finalizeAccessControlHandler()
184  {
185    aciListenerMgr.finalizeListenerManager();
186    AciEffectiveRights.finalizeOnShutdown();
187    DirectoryServer.deregisterSupportedControl(OID_GET_EFFECTIVE_RIGHTS);
188  }
189
190  @Override
191  public void initializeAccessControlHandler(
192      DseeCompatAccessControlHandlerCfg configuration)
193      throws ConfigException, InitializationException
194  {
195    initStatics();
196    DN configurationDN = configuration.dn();
197    aciList = new AciList(configurationDN);
198    aciListenerMgr = new AciListenerManager(aciList, configurationDN);
199    processGlobalAcis(configuration);
200    DirectoryServer.registerSupportedControl(OID_GET_EFFECTIVE_RIGHTS);
201  }
202
203  @Override
204  public boolean isAllowed(DN entryDN, Operation op, Control control)
205      throws DirectoryException
206  {
207    if (!skipAccessCheck(op))
208    {
209      Entry e = new Entry(entryDN, null, null, null);
210      AciContainer container = new AciLDAPOperationContainer(op, e, control,
211              ACI_READ | ACI_CONTROL);
212      if (!accessAllowed(container))
213      {
214        return false;
215      }
216    }
217
218    if (OID_PROXIED_AUTH_V2.equals(control.getOID())
219        || OID_PROXIED_AUTH_V1.equals(control.getOID()))
220    {
221      op.setAttachment(ORIG_AUTH_ENTRY, op.getAuthorizationEntry());
222    }
223    else if (OID_GET_EFFECTIVE_RIGHTS.equals(control.getOID()))
224    {
225      GetEffectiveRightsRequestControl getEffectiveRightsControl;
226      if (control instanceof LDAPControl)
227      {
228        getEffectiveRightsControl =
229            GetEffectiveRightsRequestControl.DECODER.decode(control
230                .isCritical(), ((LDAPControl) control).getValue());
231      }
232      else
233      {
234        getEffectiveRightsControl = (GetEffectiveRightsRequestControl) control;
235      }
236      op.setAttachment(OID_GET_EFFECTIVE_RIGHTS, getEffectiveRightsControl);
237    }
238    return true;
239  }
240
241  @Override
242  public boolean isAllowed(ExtendedOperation operation)
243  {
244    if (skipAccessCheck(operation))
245    {
246      return true;
247    }
248
249    Entry e = new Entry(operation.getAuthorizationDN(), null, null, null);
250    final AciContainer container =
251        new AciLDAPOperationContainer(operation, e, (ACI_READ | ACI_EXT_OP));
252    return accessAllowed(container);
253  }
254
255  @Override
256  public boolean isAllowed(LocalBackendAddOperation operation)
257      throws DirectoryException
258  {
259    AciContainer container = new AciLDAPOperationContainer(operation, ACI_ADD);
260    return isAllowed(container, operation)
261        // LDAP add needs a verify ACI syntax step in case any
262        // "aci" attribute types are being added.
263        && verifySyntax(operation.getEntryToAdd(), operation, container.getClientDN());
264  }
265
266  @Override
267  public boolean isAllowed(BindOperation bindOperation)
268  {
269    // Not planned to be implemented.
270    return true;
271  }
272
273  /**
274   * Check access on compare operations. Note that the attribute type is
275   * unavailable at this time, so this method partially parses the raw
276   * attribute string to get the base attribute type. Options are
277   * ignored.
278   *
279   * @param operation
280   *          The compare operation to check access on.
281   * @return True if access is allowed.
282   */
283  @Override
284  public boolean isAllowed(LocalBackendCompareOperation operation)
285  {
286    AciContainer container =
287        new AciLDAPOperationContainer(operation, ACI_COMPARE);
288
289    String baseName;
290    String rawAttributeType = operation.getRawAttributeType();
291    int semicolonPosition = rawAttributeType.indexOf(';');
292    if (semicolonPosition > 0)
293    {
294      baseName =
295          toLowerCase(rawAttributeType.substring(0, semicolonPosition));
296    }
297    else
298    {
299      baseName = toLowerCase(rawAttributeType);
300    }
301
302    container.setCurrentAttributeType(getSchema().getAttributeType(baseName));
303    container.setCurrentAttributeValue(operation.getAssertionValue());
304    return isAllowed(container, operation);
305  }
306
307  /**
308   * Check access on delete operations.
309   *
310   * @param operation
311   *          The delete operation to check access on.
312   * @return True if access is allowed.
313   */
314  @Override
315  public boolean isAllowed(LocalBackendDeleteOperation operation)
316  {
317    AciContainer container =
318        new AciLDAPOperationContainer(operation, ACI_DELETE);
319    return isAllowed(container, operation);
320  }
321
322  /**
323   * Checks access on a modifyDN operation.
324   *
325   * @param operation
326   *          The modifyDN operation to check access on.
327   * @return True if access is allowed.
328   */
329  @Override
330  public boolean isAllowed(ModifyDNOperation operation)
331  {
332    if (skipAccessCheck(operation))
333    {
334      return true;
335    }
336
337    final RDN oldRDN = operation.getOriginalEntry().getName().rdn();
338    final RDN newRDN = operation.getNewRDN();
339    final DN newSuperiorDN = operation.getNewSuperior();
340
341    // If this is a modifyDN move to a new superior, then check if the
342    // superior DN has import access.
343    if (newSuperiorDN != null
344        && !aciCheckSuperiorEntry(newSuperiorDN, operation))
345    {
346      return false;
347    }
348
349    // Perform the RDN access checks.
350    boolean rdnChangesAllowed = aciCheckRDNs(operation, oldRDN, newRDN);
351
352    // If this is a modifyDN move to a new superior, then check if the
353    // original entry DN has export access.
354    if (rdnChangesAllowed && newSuperiorDN != null)
355    {
356      AciContainer container = new AciLDAPOperationContainer(
357          operation, ACI_EXPORT, operation.getOriginalEntry());
358      if (!oldRDN.equals(newRDN))
359      {
360        // The RDNs are not equal, skip the proxy check since it was
361        // already performed in the aciCheckRDNs call above.
362        container.setSeenEntry(true);
363      }
364      return accessAllowed(container);
365    }
366    return rdnChangesAllowed;
367  }
368
369  @Override
370  public boolean isAllowed(LocalBackendModifyOperation operation)
371      throws DirectoryException
372  {
373    AciContainer container = new AciLDAPOperationContainer(operation, ACI_NULL);
374    return aciCheckMods(container, operation, skipAccessCheck(operation));
375  }
376
377  @Override
378  public boolean isAllowed(SearchOperation searchOperation)
379  {
380    // Not planned to be implemented.
381    return true;
382  }
383
384  @Override
385  public boolean isAllowed(Operation operation, Entry entry,
386      SearchFilter filter) throws DirectoryException
387  {
388    if (skipAccessCheck(operation))
389    {
390      return true;
391    }
392
393    AciContainer container =
394        new AciLDAPOperationContainer(operation, ACI_READ, entry);
395    return testFilter(container, filter);
396  }
397
398  @Override
399  public boolean mayProxy(Entry proxyUser, Entry proxiedUser, Operation op)
400  {
401    if (skipAccessCheck(proxyUser))
402    {
403      return true;
404    }
405
406    final AuthenticationInfo authInfo =
407        new AuthenticationInfo(proxyUser, DirectoryServer.isRootDN(proxyUser
408            .getName()));
409    final AciContainer container =
410        new AciLDAPOperationContainer(op, proxiedUser, authInfo, ACI_PROXY);
411    return accessAllowedEntry(container);
412  }
413
414  @Override
415  public boolean maySend(DN dn, Operation operation, SearchResultReference reference)
416  {
417    if (skipAccessCheck(operation))
418    {
419      return true;
420    }
421
422    // Load the values, a bind rule might want to evaluate them.
423    final AttributeBuilder builder = new AttributeBuilder(refAttrType);
424    builder.addAllStrings(reference.getReferralURLs());
425
426    final Entry e = new Entry(dn, null, null, null);
427    e.addAttribute(builder.toAttribute(), null);
428    final SearchResultEntry se = new SearchResultEntry(e);
429    final AciContainer container =
430        new AciLDAPOperationContainer(operation, ACI_READ, se);
431    container.setCurrentAttributeType(refAttrType);
432    return accessAllowed(container);
433  }
434
435  @Override
436  public boolean maySend(Operation operation, SearchResultEntry entry)
437  {
438    if (skipAccessCheck(operation))
439    {
440      return true;
441    }
442
443    AciContainer container =
444        new AciLDAPOperationContainer(operation, ACI_SEARCH, entry);
445
446    // Pre/post read controls are associated with other types of operation.
447    if (operation instanceof SearchOperation)
448    {
449      try
450      {
451        if (!testFilter(container, ((SearchOperation) operation).getFilter()))
452        {
453          return false;
454        }
455      }
456      catch (DirectoryException ex)
457      {
458        return false;
459      }
460    }
461
462    container.clearEvalAttributes(ACI_NULL);
463    container.setRights(ACI_READ);
464
465    if (!accessAllowedEntry(container))
466    {
467      return false;
468    }
469
470    if (!container.hasEvalUserAttributes())
471    {
472      operation.setAttachment(ALL_USER_ATTRS_MATCHED, ALL_USER_ATTRS_MATCHED);
473    }
474    if (!container.hasEvalOpAttributes())
475    {
476      operation.setAttachment(ALL_OP_ATTRS_MATCHED, ALL_OP_ATTRS_MATCHED);
477    }
478
479    return true;
480  }
481
482  /**
483   * Check access using the specified container. This container will
484   * have all of the information to gather applicable ACIs and perform
485   * evaluation on them.
486   *
487   * @param container
488   *          An ACI operation container which has all of the
489   *          information needed to check access.
490   * @return True if access is allowed.
491   */
492  boolean accessAllowed(AciContainer container)
493  {
494    DN dn = container.getResourceDN();
495    // For ACI_WRITE_ADD and ACI_WRITE_DELETE set the ACI_WRITE
496    // right.
497    if (container.hasRights(ACI_WRITE_ADD)
498        || container.hasRights(ACI_WRITE_DELETE))
499    {
500      container.setRights(container.getRights() | ACI_WRITE);
501    }
502    // Check if the ACI_SELF right needs to be set (selfwrite right).
503    // Only done if the right is ACI_WRITE, an attribute value is set
504    // and that attribute value is a DN.
505    if (container.getCurrentAttributeValue() != null
506        && container.hasRights(ACI_WRITE)
507        && isAttributeDN(container.getCurrentAttributeType()))
508    {
509      String dnString = null;
510      try
511      {
512        dnString = container.getCurrentAttributeValue().toString();
513        DN tmpDN = DN.valueOf(dnString);
514        // Have a valid DN, compare to clientDN to see if the ACI_SELF
515        // right should be set.
516        if (tmpDN.equals(container.getClientDN()))
517        {
518          container.setRights(container.getRights() | ACI_SELF);
519        }
520      }
521      catch (LocalizedIllegalArgumentException ex)
522      {
523        // Log a message and keep going.
524        logger.warn(WARN_ACI_NOT_VALID_DN, dnString);
525      }
526    }
527
528    // First get all allowed candidate ACIs.
529    List<Aci> candidates = aciList.getCandidateAcis(dn);
530    /*
531     * Create an applicable list of ACIs by target matching each
532     * candidate ACI against the container's target match view.
533     */
534    createApplicableList(candidates, container);
535    // Evaluate the applicable list.
536    final boolean ret = testApplicableLists(container);
537    // Build summary string if doing geteffectiverights eval.
538    if (container.isGetEffectiveRightsEval())
539    {
540      container.setEvalSummary(
541          AciEffectiveRights.createSummary(container, ret));
542    }
543    return ret;
544  }
545
546  /*
547   * TODO Evaluate performance of this method. TODO Evaluate security
548   * concerns of this method. Logic from this method taken almost
549   * directly from DS6 implementation. I find the work done in the
550   * accessAllowedEntry method, particularly with regard to the entry
551   * test evaluation, to be very confusing and potentially pretty
552   * inefficient. I'm also concerned that the "return "true" inside the
553   * for loop could potentially allow access when it should be denied.
554   */
555
556  /**
557   * Check if access is allowed on an entry. Access is checked by
558   * iterating through each attribute of an entry, starting with the
559   * "objectclass" attribute type. If access is allowed on the entry
560   * based on one of it's attribute types, then a possible second access
561   * check is performed. This second check is only performed if an entry
562   * test ACI was found during the earlier successful access check. An
563   * entry test ACI has no "targetattrs" keyword, so allowing access
564   * based on an attribute type only would be incorrect.
565   *
566   * @param container
567   *          ACI search container containing all of the information
568   *          needed to check access.
569   * @return True if access is allowed.
570   */
571  boolean accessAllowedEntry(AciContainer container)
572  {
573    // set flag that specifies this is the first attribute evaluated
574    // in the entry
575    container.setIsFirstAttribute(true);
576    for (AttributeType attrType : getAllAttrs(container.getResourceEntry()))
577    {
578      /*
579       * Check if access is allowed. If true, then check to see if an
580       * entry test rule was found (no targetattrs) during target match
581       * evaluation. If such a rule was found, set the current attribute
582       * type to "null" and check access again so that rule is applied.
583       */
584      container.setCurrentAttributeType(attrType);
585      if (accessAllowed(container))
586      {
587        if (container.hasEntryTestRule())
588        {
589          container.setCurrentAttributeType(null);
590          if (!accessAllowed(container) && container.isDenyEval())
591          {
592            /*
593             * If we failed because of a deny permission-bind rule, we need to
594             * stop and return false.
595             * If we failed because there was no explicit allow rule, then we
596             * grant implicit access to the entry.
597             */
598            return false;
599          }
600        }
601        return true;
602      }
603    }
604    return false;
605  }
606
607  /**
608   * Performs an access check against all of the attributes of an entry. The
609   * attributes that fail access are removed from the entry. This method
610   * performs the processing needed for the filterEntry method processing.
611   *
612   * @param container
613   *          The search or compare container which has all of the information
614   *          needed to filter the attributes for this entry.
615   * @param filteredEntry
616   *          The partially filtered search result entry being returned to the
617   *          client.
618   */
619  private void filterEntry(AciContainer container, Entry filteredEntry)
620  {
621    for (AttributeType attrType : getAllAttrs(filteredEntry))
622    {
623      if (container.hasAllUserAttributes() && !attrType.isOperational())
624      {
625        continue;
626      }
627      if (container.hasAllOpAttributes() && attrType.isOperational())
628      {
629        continue;
630      }
631      container.setCurrentAttributeType(attrType);
632      if (!accessAllowed(container))
633      {
634        filteredEntry.removeAttribute(attrType);
635      }
636    }
637  }
638
639  /**
640   * Checks to see if a LDAP modification is allowed access.
641   *
642   * @param container
643   *          The structure containing the LDAP modifications
644   * @param operation
645   *          The operation to check modify privileges on. operation to
646   *          check and the evaluation context to apply the check
647   *          against.
648   * @param skipAccessCheck
649   *          True if access checking should be skipped.
650   * @return True if access is allowed.
651   * @throws DirectoryException
652   *           If a modified ACI could not be decoded.
653   */
654  private boolean aciCheckMods(AciContainer container,
655      LocalBackendModifyOperation operation, boolean skipAccessCheck)
656      throws DirectoryException
657  {
658    Entry resourceEntry = container.getResourceEntry();
659    DN dn = resourceEntry.getName();
660    List<Modification> modifications =  operation.getModifications();
661
662    for (Modification m : modifications)
663    {
664      Attribute modAttr = m.getAttribute();
665      AttributeType modAttrType = modAttr.getAttributeDescription().getAttributeType();
666
667      if (modAttrType.equals(aciType)
668          /*
669           * Check that the operation has modify privileges if it contains an "aci" attribute type.
670           */
671          && !operation.getClientConnection().hasPrivilege(
672              Privilege.MODIFY_ACL, operation))
673      {
674        logger.debug(INFO_ACI_MODIFY_FAILED_PRIVILEGE, container.getResourceDN(), container.getClientDN());
675        return false;
676      }
677      // This access check handles the case where all attributes of this
678      // type are being replaced or deleted. If only a subset is being
679      // deleted than this access check is skipped.
680      ModificationType modType = m.getModificationType();
681      if (((modType == ModificationType.DELETE && modAttr.isEmpty())
682              || modType == ModificationType.REPLACE
683              || modType == ModificationType.INCREMENT)
684          /*
685           * Check if we have rights to delete all values of an attribute type in the resource
686           * entry.
687           */
688          && resourceEntry.hasAttribute(modAttrType))
689      {
690        container.setCurrentAttributeType(modAttrType);
691        for (Attribute a : resourceEntry.getAttribute(modAttr.getAttributeDescription()))
692        {
693          for (ByteString v : a)
694          {
695            container.setCurrentAttributeValue(v);
696            container.setRights(ACI_WRITE_DELETE);
697            if (!skipAccessCheck && !accessAllowed(container))
698            {
699              return false;
700            }
701          }
702        }
703      }
704
705      if (!modAttr.isEmpty())
706      {
707        for (ByteString v : modAttr)
708        {
709          container.setCurrentAttributeType(modAttrType);
710          switch (m.getModificationType().asEnum())
711          {
712          case ADD:
713          case REPLACE:
714            container.setCurrentAttributeValue(v);
715            container.setRights(ACI_WRITE_ADD);
716            if (!skipAccessCheck && !accessAllowed(container))
717            {
718              return false;
719            }
720            break;
721          case DELETE:
722            container.setCurrentAttributeValue(v);
723            container.setRights(ACI_WRITE_DELETE);
724            if (!skipAccessCheck && !accessAllowed(container))
725            {
726              return false;
727            }
728            break;
729          case INCREMENT:
730            Entry modifiedEntry = operation.getModifiedEntry();
731            for (Attribute attr : modifiedEntry.getAttribute(modAttr.getAttributeDescription()))
732            {
733              for (ByteString val : attr)
734              {
735                container.setCurrentAttributeValue(val);
736                container.setRights(ACI_WRITE_ADD);
737                if (!skipAccessCheck && !accessAllowed(container))
738                {
739                  return false;
740                }
741              }
742            }
743            break;
744          }
745          /*
746           * Check if the modification type has an "aci" attribute type.
747           * If so, check the syntax of that attribute value. Fail the
748           * the operation if the syntax check fails.
749           */
750          if (modAttrType.equals(aciType)
751              || modAttrType.equals(globalAciType))
752          {
753            try
754            {
755              // A global ACI needs a NULL DN, not the DN of the
756              // modification.
757              if (modAttrType.equals(globalAciType))
758              {
759                dn = DN.rootDN();
760              }
761              // validate ACI syntax
762              Aci.decode(v, dn);
763            }
764            catch (AciException ex)
765            {
766              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
767                  WARN_ACI_MODIFY_FAILED_DECODE.get(dn, ex.getMessage()));
768            }
769          }
770        }
771      }
772    }
773    return true;
774  }
775
776  /**
777   * Perform all needed RDN checks for the modifyDN operation. The old RDN is
778   * not equal to the new RDN. The access checks are:
779   * <ul>
780   * <li>Verify WRITE access to the original entry.</li>
781   * <li>Verify WRITE_ADD access on each RDN component of the new RDN. The
782   * WRITE_ADD access is used because this access could be restricted by the
783   * targattrfilters keyword.</li>
784   * <li>If the deleteOLDRDN flag is set, verify WRITE_DELETE access on the old
785   * RDN. The WRITE_DELETE access is used because this access could be
786   * restricted by the targattrfilters keyword.
787   * <li>
788   * </ul>
789   *
790   * @param operation
791   *          The ModifyDN operation class containing information to check
792   *          access on.
793   * @param oldRDN
794   *          The old RDN component.
795   * @param newRDN
796   *          The new RDN component.
797   * @return True if access is allowed.
798   */
799  private boolean aciCheckRDNs(ModifyDNOperation operation,
800      RDN oldRDN, RDN newRDN)
801  {
802    AciContainer container =
803        new AciLDAPOperationContainer(operation, ACI_WRITE, operation
804            .getOriginalEntry());
805    if (!accessAllowed(container))
806    {
807      return false;
808    }
809
810    boolean ret = checkRDN(ACI_WRITE_ADD, newRDN, container);
811    if (ret && operation.deleteOldRDN())
812    {
813      ret = checkRDN(ACI_WRITE_DELETE, oldRDN, container);
814    }
815    return ret;
816  }
817
818  /**
819   * Check access on the new superior entry if it exists. If superiordn is null,
820   * the entry does not exist or the DN cannot be locked then false is returned.
821   *
822   * @param superiorDN
823   *          The DN of the new superior entry.
824   * @param op
825   *          The modifyDN operation to check access on.
826   * @return True if access is granted to the new superior entry.
827   */
828  private boolean aciCheckSuperiorEntry(DN superiorDN, ModifyDNOperation op)
829  {
830    try
831    {
832      Entry superiorEntry = DirectoryServer.getEntry(superiorDN);
833      if (superiorEntry != null)
834      {
835        AciContainer container =
836            new AciLDAPOperationContainer(op, ACI_IMPORT, superiorEntry);
837        return accessAllowed(container);
838      }
839      return false;
840    }
841    catch (DirectoryException ex)
842    {
843      return false;
844    }
845  }
846
847  /**
848   * Check access on each attribute-value pair component of the
849   * specified RDN. There may be more than one attribute-value pair if
850   * the RDN is multi-valued.
851   *
852   * @param right
853   *          The access right to check for.
854   * @param rdn
855   *          The RDN to examine the attribute-value pairs of.
856   * @param container
857   *          The container containing the information needed to
858   *          evaluate the specified RDN.
859   * @return True if access is allowed for all attribute-value pairs.
860   */
861  private boolean checkRDN(int right, RDN rdn, AciContainer container)
862  {
863    container.setRights(right);
864    for (AVA ava : rdn)
865    {
866      container.setCurrentAttributeType(ava.getAttributeType());
867      container.setCurrentAttributeValue(ava.getAttributeValue());
868      if (!accessAllowed(container))
869      {
870        return false;
871      }
872    }
873    return true;
874  }
875
876  /**
877   * Creates the allow and deny ACI lists based on the provided target
878   * match context. These lists are stored in the evaluation context.
879   *
880   * @param candidates
881   *          List of all possible ACI candidates.
882   * @param targetMatchCtx
883   *          Target matching context to use for testing each ACI.
884   */
885  private void createApplicableList(List<Aci> candidates,
886      AciTargetMatchContext targetMatchCtx)
887  {
888    List<Aci> denys = new LinkedList<>();
889    List<Aci> allows = new LinkedList<>();
890    for (Aci aci : candidates)
891    {
892      if (Aci.isApplicable(aci, targetMatchCtx))
893      {
894        if (aci.hasAccessType(EnumAccessType.DENY))
895        {
896          denys.add(aci);
897        }
898        if (aci.hasAccessType(EnumAccessType.ALLOW))
899        {
900          allows.add(aci);
901        }
902      }
903      if (targetMatchCtx.getTargAttrFiltersMatch())
904      {
905        targetMatchCtx.setTargAttrFiltersMatch(false);
906      }
907    }
908    targetMatchCtx.setAllowList(allows);
909    targetMatchCtx.setDenyList(denys);
910  }
911
912  /**
913   * Gathers all of the attribute types in an entry along with the
914   * "objectclass" attribute type in a List. The "objectclass" attribute
915   * is added to the list first so it is evaluated first.
916   *
917   * @param e
918   *          Entry to gather the attributes for.
919   * @return List containing the attribute types.
920   */
921  private List<AttributeType> getAllAttrs(Entry e)
922  {
923    List<AttributeType> typeList = new LinkedList<>();
924    /*
925     * When a search is not all attributes returned, the "objectclass"
926     * attribute type is missing from the entry.
927     */
928    final Attribute attr = e.getObjectClassAttribute();
929    if (attr != null)
930    {
931      typeList.add(attr.getAttributeDescription().getAttributeType());
932    }
933    typeList.addAll(e.getUserAttributes().keySet());
934    typeList.addAll(e.getOperationalAttributes().keySet());
935    return typeList;
936  }
937
938  /**
939   * Check access using the accessAllowed method. The LDAP add, compare,
940   * modify and delete operations use this function. The other supported
941   * LDAP operations have more specialized checks.
942   *
943   * @param container
944   *          The container containing the information needed to
945   *          evaluate this operation.
946   * @param operation
947   *          The operation being evaluated.
948   * @return True if this operation is allowed access.
949   */
950  private boolean isAllowed(AciContainer container, Operation operation)
951  {
952    return skipAccessCheck(operation) || accessAllowed(container);
953  }
954
955  /**
956   * Check if the specified attribute type is a DN by checking if its
957   * syntax OID is equal to the DN syntax OID.
958   *
959   * @param attribute
960   *          The attribute type to check.
961   * @return True if the attribute type syntax OID is equal to a DN
962   *         syntax OID.
963   */
964  private boolean isAttributeDN(AttributeType attribute)
965  {
966    return SYNTAX_DN_OID.equals(attribute.getSyntax().getOID());
967  }
968
969  /**
970   * Process all global ACI attribute types found in the configuration
971   * entry and adds them to that ACI list cache. It also logs messages
972   * about the number of ACI attribute types added to the cache. This
973   * method is called once at startup. It also will put the server into
974   * lockdown mode if needed.
975   *
976   * @param configuration
977   *          The config handler containing the ACI configuration
978   *          information.
979   * @throws InitializationException
980   *           If there is an error reading the global ACIs from the
981   *           configuration entry.
982   */
983  private void processGlobalAcis(
984      DseeCompatAccessControlHandlerCfg configuration)
985      throws InitializationException
986  {
987    try
988    {
989      final SortedSet<Aci> globalAcis = new TreeSet<>();
990      for (String value : configuration.getGlobalACI())
991      {
992        globalAcis.add(Aci.decode(ByteString.valueOfUtf8(value), DN.rootDN()));
993      }
994      if (!globalAcis.isEmpty())
995      {
996        aciList.addAci(DN.rootDN(), globalAcis);
997        logger.debug(INFO_ACI_ADD_LIST_GLOBAL_ACIS, globalAcis.size());
998      }
999    }
1000    catch (Exception e)
1001    {
1002      logger.traceException(e);
1003      throw new InitializationException(
1004          INFO_ACI_HANDLER_FAIL_PROCESS_GLOBAL_ACI.get(configuration.dn()), e);
1005    }
1006  }
1007
1008  /**
1009   * Check to see if the specified entry has the specified privilege.
1010   *
1011   * @param e
1012   *          The entry to check privileges on.
1013   * @return {@code true} if the entry has the specified privilege, or
1014   *         {@code false} if not.
1015   */
1016  private boolean skipAccessCheck(Entry e)
1017  {
1018    return ClientConnection.hasPrivilege(e, Privilege.BYPASS_ACL);
1019  }
1020
1021  /**
1022   * Check to see if the client entry has BYPASS_ACL privileges for this
1023   * operation.
1024   *
1025   * @param operation
1026   *          The operation to check privileges on.
1027   * @return True if access checking can be skipped because the
1028   *         operation client connection has BYPASS_ACL privileges.
1029   */
1030  private boolean skipAccessCheck(Operation operation)
1031  {
1032    return operation.getClientConnection().hasPrivilege(
1033        Privilege.BYPASS_ACL, operation);
1034  }
1035
1036  /**
1037   * Performs the test of the deny and allow access lists using the
1038   * provided evaluation context. The deny list is checked first.
1039   *
1040   * @param evalCtx
1041   *          The evaluation context to use.
1042   * @return True if access is allowed.
1043   */
1044  private boolean testApplicableLists(AciEvalContext evalCtx)
1045  {
1046    evalCtx.setEvaluationResult(NO_REASON, null);
1047
1048    if (evalCtx.getAllowList().isEmpty()
1049        && (!evalCtx.isGetEffectiveRightsEval()
1050            || evalCtx.hasRights(ACI_SELF)
1051            || !evalCtx.isTargAttrFilterMatchAciEmpty()))
1052    {
1053      // If allows list is empty and not doing geteffectiverights return false.
1054      evalCtx.setEvaluationResult(NO_ALLOW_ACIS, null);
1055      return false;
1056    }
1057
1058    for (Aci denyAci : evalCtx.getDenyList())
1059    {
1060      final EnumEvalResult res = Aci.evaluate(evalCtx, denyAci);
1061      // Failure could be returned if a system limit is hit or
1062      // search fails
1063      if (EnumEvalResult.FAIL.equals(res))
1064      {
1065        evalCtx.setEvaluationResult(EVALUATED_DENY_ACI, denyAci);
1066        return false;
1067      }
1068      else if (EnumEvalResult.TRUE.equals(res))
1069      {
1070        if (testAndSetTargAttrOperationMatches(evalCtx, denyAci, true))
1071        {
1072          continue;
1073        }
1074        evalCtx.setEvaluationResult(EVALUATED_DENY_ACI, denyAci);
1075        return false;
1076      }
1077    }
1078
1079    for (Aci allowAci : evalCtx.getAllowList())
1080    {
1081      final EnumEvalResult res = Aci.evaluate(evalCtx, allowAci);
1082      if (EnumEvalResult.TRUE.equals(res))
1083      {
1084        if (testAndSetTargAttrOperationMatches(evalCtx, allowAci, false))
1085        {
1086          continue;
1087        }
1088        evalCtx.setEvaluationResult(EVALUATED_ALLOW_ACI, allowAci);
1089        return true;
1090      }
1091    }
1092    // Nothing matched fall through.
1093    evalCtx.setEvaluationResult(NO_MATCHED_ALLOWS_ACIS, null);
1094    return false;
1095  }
1096
1097  private boolean testAndSetTargAttrOperationMatches(AciEvalContext evalCtx,
1098      Aci aci, boolean isDenyAci)
1099  {
1100    return evalCtx.isGetEffectiveRightsEval()
1101        && !evalCtx.hasRights(ACI_SELF)
1102        && !evalCtx.isTargAttrFilterMatchAciEmpty()
1103        // Iterate to next only if ACI contains a targattrfilters keyword.
1104        && AciEffectiveRights.setTargAttrAci(evalCtx, aci, isDenyAci);
1105  }
1106
1107  /**
1108   * Test the attribute types of the search filter for access. This
1109   * method supports the search right.
1110   *
1111   * @param container
1112   *          The container used in the access evaluation.
1113   * @param filter
1114   *          The filter to check access on.
1115   * @return True if all attribute types in the filter have access.
1116   * @throws DirectoryException
1117   *           If there is a problem matching the entry using the
1118   *           provided filter.
1119   */
1120  private boolean testFilter(AciContainer container, SearchFilter filter)
1121      throws DirectoryException
1122  {
1123    // If the resource entry has a dn equal to "cn=debugsearch" and it
1124    // contains the special attribute type "debugsearchindex", then the
1125    // resource entry is a pseudo entry created for debug purposes.
1126    // Return true if that is the case.
1127    if (debugSearchIndexDN.equals(container.getResourceDN())
1128        && container.getResourceEntry().hasAttribute(debugSearchIndex))
1129    {
1130      return true;
1131    }
1132    switch (filter.getFilterType())
1133    {
1134    case AND:
1135    case OR:
1136    {
1137      for (SearchFilter f : filter.getFilterComponents())
1138      {
1139        if (!testFilter(container, f))
1140        {
1141          return false;
1142        }
1143      }
1144      break;
1145    }
1146    case NOT:
1147    {
1148      return testFilter(container, filter.getNotComponent());
1149    }
1150    default:
1151    {
1152      container.setCurrentAttributeType(filter.getAttributeType());
1153      return accessAllowed(container);
1154    }
1155    }
1156    return true;
1157  }
1158
1159  /**
1160   * Evaluate an entry to be added to see if it has any "aci" attribute
1161   * type. If it does, examines each "aci" attribute type value for
1162   * syntax errors. All of the "aci" attribute type values must pass
1163   * syntax check for the add operation to proceed. Any entry with an
1164   * "aci" attribute type must have "modify-acl" privileges.
1165   *
1166   * @param entry
1167   *          The entry to be examined.
1168   * @param operation
1169   *          The operation to to check privileges on.
1170   * @param clientDN
1171   *          The authorization DN.
1172   * @return True if the entry has no ACI attributes or if all of the
1173   *         "aci" attributes values pass ACI syntax checking.
1174   * @throws DirectoryException
1175   *           If a modified ACI could not be decoded.
1176   */
1177  private boolean verifySyntax(Entry entry, Operation operation,
1178      DN clientDN) throws DirectoryException
1179  {
1180    if (entry.hasOperationalAttribute(aciType))
1181    {
1182      /*
1183       * Check that the operation has "modify-acl" privileges since the
1184       * entry to be added has an "aci" attribute type.
1185       */
1186      if (!operation.getClientConnection().hasPrivilege(
1187          Privilege.MODIFY_ACL, operation))
1188      {
1189        logger.debug(INFO_ACI_ADD_FAILED_PRIVILEGE, entry.getName(), clientDN);
1190        return false;
1191      }
1192      List<Attribute> attributeList = entry.getOperationalAttribute(AttributeDescription.create(aciType));
1193      for (Attribute attribute : attributeList)
1194      {
1195        for (ByteString value : attribute)
1196        {
1197          try
1198          {
1199            // validate ACI syntax
1200            Aci.decode(value, entry.getName());
1201          }
1202          catch (AciException ex)
1203          {
1204            throw new DirectoryException(
1205                ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1206                WARN_ACI_ADD_FAILED_DECODE.get(entry.getName(), ex.getMessage()));
1207          }
1208        }
1209      }
1210    }
1211    return true;
1212  }
1213}