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