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