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