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    import org.opends.server.api.Backend;
032    import static org.opends.server.authorization.dseecompat.AciHandler.*;
033    import static org.opends.server.loggers.ErrorLogger.logError;
034    import static org.opends.messages.AccessControlMessages.*;
035    import org.opends.server.types.*;
036    
037    import java.util.*;
038    
039    /**
040     * The AciList class performs caching of the ACI attribute values
041     * using the entry DN as the key.
042     */
043    public class AciList {
044    
045      /*
046       * A map containing all the ACIs.
047       * We use the copy-on-write technique to avoid locking when reading.
048       */
049      private volatile LinkedHashMap<DN, List<Aci>> aciList =
050           new LinkedHashMap<DN, List<Aci>>();
051    
052      /*
053      * The configuration DN used to compare against the global ACI entry DN.
054      */
055      private DN configDN;
056    
057      /**
058       * Constructor to create an ACI list to cache ACI attribute types.
059       * @param configDN The configuration entry DN.
060       */
061      public AciList(DN configDN) {
062         this.configDN=configDN;
063      }
064    
065      /**
066       * Accessor to the ACI list intended to be called from within unsynchronized
067       * read-only methods.
068       * @return   The current ACI list.
069       */
070      private LinkedHashMap<DN, List<Aci>> getList() {
071        return aciList;
072      }
073    
074      /**
075       * Used by synchronized write methods to make a copy of the ACI list.
076       * @return A copy of the ACI list.
077       */
078      private LinkedHashMap<DN,List<Aci>> copyList() {
079        return new LinkedHashMap<DN, List<Aci>>(aciList);
080      }
081    
082      /**
083       * Using the base DN, return a list of ACIs that are candidates for
084       * evaluation by walking up from the base DN towards the root of the
085       * DIT gathering ACIs on parents. Global ACIs use the NULL DN as the key
086       * and are included in the candidate set only if they have no
087       * "target" keyword rules, or if the target keyword rule matches for
088       * the specified base DN.
089       *
090       * @param baseDN  The DN to check.
091       * @return A list of candidate ACIs that might be applicable.
092       */
093      public LinkedList<Aci> getCandidateAcis(DN baseDN) {
094        LinkedList<Aci> candidates = new LinkedList<Aci>();
095        if(baseDN == null)
096          return candidates;
097    
098        // Save a reference to the current ACI list, in case it gets changed.
099        LinkedHashMap<DN, List<Aci>> aciList = getList();
100        //Save the baseDN in case we need to evaluate a global ACI.
101        DN entryDN=baseDN;
102        while(baseDN != null) {
103          List<Aci> acis = aciList.get(baseDN);
104          if (acis != null) {
105           //Check if there are global ACIs. Global ACI has a NULL DN.
106           if(baseDN.isNullDN()) {
107               for(Aci aci : acis) {
108                   AciTargets targets=aci.getTargets();
109                   //If there is a target, evaluate it to see if this ACI should
110                   //be included in the candidate set.
111                   if(targets != null) {
112                       boolean ret=AciTargets.isTargetApplicable(aci, targets,
113                                                                 entryDN);
114                       if(ret)
115                          candidates.add(aci);  //Add this ACI to the candidates.
116                   }
117               }
118           } else
119               candidates.addAll(acis);
120          }
121          if(baseDN.isNullDN())
122            break;
123          DN parentDN=baseDN.getParent();
124          if(parentDN == null)
125            baseDN=DN.nullDN();
126          else
127            baseDN=parentDN;
128        }
129        return candidates;
130      }
131    
132      /**
133       * Add all the ACI from a set of entries to the ACI list. There is no need
134       * to check for global ACIs since they are processe by the AciHandler at
135       * startup using the addACi single entry method.
136       * @param entries The set of entries containing the "aci" attribute values.
137       * @param failedACIMsgs List that will hold error messages from ACI decode
138       *                      exceptions.
139       * @return The number of valid ACI attribute values added to the ACI list.
140       */
141      public synchronized int addAci(List<? extends Entry> entries,
142                                     LinkedList<Message> failedACIMsgs)
143      {
144        // Copy the ACI list.
145        LinkedHashMap<DN,List<Aci>> aciCopy = copyList();
146    
147        int validAcis=0;
148        for (Entry entry : entries) {
149          DN dn=entry.getDN();
150          List<Attribute> attributeList =
151               entry.getOperationalAttribute(AciHandler.aciType);
152          validAcis += addAciAttributeList(aciCopy, dn, configDN,
153                                           attributeList, failedACIMsgs);
154        }
155    
156        // Replace the ACI list with the copy.
157        aciList = aciCopy;
158        return validAcis;
159      }
160    
161      /**
162       * Add a set of ACIs to the ACI list. This is usually used a startup, when
163       * global ACIs are processed.
164       *
165       * @param dn The DN to add the ACIs under.
166       *
167       * @param acis A set of ACIs to add to the ACI list.
168       *
169       */
170      public synchronized void addAci(DN dn, SortedSet<Aci> acis) {
171        aciList.put(dn, new LinkedList<Aci>(acis));
172      }
173    
174      /**
175       * Add all of an entry's ACI (global or regular) attribute values to the
176       * ACI list.
177       * @param entry The entry containing the ACI attributes.
178       * @param hasAci True if the "aci" attribute type was seen in the entry.
179       * @param hasGlobalAci True if the "ds-cfg-global-aci" attribute type was
180       * seen in the entry.
181       * @param failedACIMsgs List that will hold error messages from ACI decode
182       *                      exceptions.
183       * @return The number of valid ACI attribute values added to the ACI list.
184       */
185      public synchronized int addAci(Entry entry,  boolean hasAci,
186                                     boolean hasGlobalAci,
187                                     LinkedList<Message> failedACIMsgs) {
188        int validAcis=0;
189    
190        // Copy the ACI list.
191        LinkedHashMap<DN,List<Aci>> aciCopy = copyList();
192        //Process global "ds-cfg-global-aci" attribute type. The oldentry
193        //DN is checked to verify it is equal to the config DN. If not those
194        //attributes are skipped.
195        if(hasGlobalAci && entry.getDN().equals(configDN)) {
196            List<Attribute> attributeList = entry.getAttribute(globalAciType);
197            validAcis = addAciAttributeList(aciCopy, DN.nullDN(), configDN,
198                                            attributeList, failedACIMsgs);
199        }
200    
201        if(hasAci) {
202            List<Attribute> attributeList = entry.getAttribute(aciType);
203            validAcis += addAciAttributeList(aciCopy, entry.getDN(), configDN,
204                                             attributeList, failedACIMsgs);
205        }
206        // Replace the ACI list with the copy.
207        aciList = aciCopy;
208        return validAcis;
209      }
210    
211      /**
212       * Add an ACI's attribute type values to the ACI list. There is a chance that
213       * an ACI will throw an exception if it has an invalid syntax. If that
214       * happens a message will be logged and the ACI skipped.  A count is
215       * returned of the number of valid ACIs added.
216       * @param aciList The ACI list to which the ACI is to be added.
217       * @param dn The DN to use as the key in the ACI list.
218       * @param configDN The DN of the configuration entry used to configure the
219       *                 ACI handler. Used if a global ACI has an decode exception.
220       * @param attributeList List of attributes containing the ACI attribute
221       * values.
222       * @param failedACIMsgs List that will hold error messages from ACI decode
223       *                      exceptions.
224       * @return The number of valid attribute values added to the ACI list.
225       */
226      private static int addAciAttributeList(LinkedHashMap<DN,List<Aci>> aciList,
227                                             DN dn, DN configDN,
228                                             List<Attribute> attributeList,
229                                             LinkedList<Message> failedACIMsgs) {
230    
231        if (attributeList == null) {
232          return 0;
233        }
234    
235        int validAcis=0;
236        ArrayList<Aci> acis = new ArrayList<Aci>();
237        for (Attribute attribute : attributeList) {
238          for (AttributeValue value : attribute.getValues()) {
239            try {
240              Aci aci= Aci.decode(value.getValue(),dn);
241              acis.add(aci);
242              validAcis++;
243            } catch (AciException ex) {
244              DN msgDN=dn;
245              if(dn == DN.nullDN()) {
246                msgDN=configDN;
247              }
248              Message message = WARN_ACI_ADD_LIST_FAILED_DECODE.get(
249                      value.getValue().toString(),
250                      String.valueOf(msgDN),
251                      ex.getMessage());
252              failedACIMsgs.add(message);
253            }
254          }
255        }
256        addAci(aciList, dn, acis);
257        return validAcis;
258      }
259    
260      /**
261       * Remove all of the ACIs related to the old entry and then add all of the
262       * ACIs related to the new entry. This method locks/unlocks the list.
263       * In the case of global ACIs the DN of the entry is checked to make sure it
264       * is equal to the config DN. If not, the global ACI attribute type is
265       * silently skipped.
266       * @param oldEntry The old entry possibly containing old ACI attribute
267       * values.
268       * @param newEntry The new entry possibly containing new ACI attribute
269       * values.
270       * @param hasAci True if the "aci" attribute type was seen in the entry.
271       * @param hasGlobalAci True if the "ds-cfg-global-aci" attribute type was
272       * seen in the entry.
273       */
274      public synchronized void modAciOldNewEntry(Entry oldEntry, Entry newEntry,
275                                                 boolean hasAci,
276                                                 boolean hasGlobalAci) {
277    
278          // Copy the ACI list.
279          LinkedHashMap<DN,List<Aci>> aciCopy = copyList();
280          LinkedList<Message>failedACIMsgs=new LinkedList<Message>();
281          //Process "aci" attribute types.
282          if(hasAci) {
283              aciCopy.remove(oldEntry.getDN());
284              List<Attribute> attributeList =
285                      newEntry.getOperationalAttribute(aciType);
286              addAciAttributeList(aciCopy,newEntry.getDN(), configDN,
287                                  attributeList, failedACIMsgs);
288          }
289          //Process global "ds-cfg-global-aci" attribute type. The oldentry
290          //DN is checked to verify it is equal to the config DN. If not those
291          //attributes are skipped.
292          if(hasGlobalAci && oldEntry.getDN().equals(configDN)) {
293              aciCopy.remove(DN.nullDN());
294              List<Attribute> attributeList =
295                      newEntry.getAttribute(globalAciType);
296              addAciAttributeList(aciCopy, DN.nullDN(), configDN,
297                                  attributeList, failedACIMsgs);
298          }
299          // Replace the ACI list with the copy.
300          aciList = aciCopy;
301      }
302    
303      /**
304       * Add ACI using the DN as a key. If the DN already
305       * has ACI(s) on the list, then the new ACI is added to the
306       * end of the array.
307       * @param aciList The set of ACIs to which ACI is to be added.
308       * @param dn The DN to use as the key.
309       * @param acis The ACI to be added.
310       */
311      private static void addAci(LinkedHashMap<DN,List<Aci>> aciList, DN dn,
312                                 List<Aci> acis)
313      {
314        if(aciList.containsKey(dn)) {
315          List<Aci> tmpAci = aciList.get(dn);
316          tmpAci.addAll(acis);
317        } else {
318          aciList.put(dn, acis);
319        }
320      }
321    
322      /**
323       * Remove global and regular ACIs from the list. It's possible that an entry
324       * could have both attribute types (aci and ds-cfg-global-aci). Global ACIs
325       * use the NULL DN for the key.  In the case of global ACIs the DN of the
326       * entry is checked to make sure it is equal to the config DN. If not, the
327       * global ACI attribute type is silently skipped.
328       * @param entry The entry containing the global ACIs.
329       * @param hasAci True if the "aci" attribute type was seen in the entry.
330       * @param hasGlobalAci True if the "ds-cfg-global-aci" attribute type was
331       * seen in the entry.
332       * @return  True if the ACI set was deleted.
333       */
334      public synchronized boolean removeAci(Entry entry,  boolean hasAci,
335                                                          boolean hasGlobalAci) {
336          // Copy the ACI list.
337          LinkedHashMap<DN,List<Aci>> aciCopy = copyList();
338    
339          if(hasGlobalAci && entry.getDN().equals(configDN) &&
340             aciCopy.remove(DN.nullDN()) == null)
341              return false;
342          if(hasAci && aciCopy.remove(entry.getDN()) == null)
343              return false;
344          // Replace the ACI list with the copy.
345          aciList = aciCopy;
346          return true;
347      }
348    
349      /**
350       * Remove all ACIs related to a backend.
351       * @param backend  The backend to check if each DN is handled by that
352       * backend.
353       */
354      public synchronized void removeAci(Backend backend) {
355        // Copy the ACI list.
356        LinkedHashMap<DN,List<Aci>> aciCopy = copyList();
357    
358        Iterator<Map.Entry<DN,List<Aci>>> iterator = aciCopy.entrySet().iterator();
359        while (iterator.hasNext())
360        {
361          Map.Entry<DN,List<Aci>> mapEntry = iterator.next();
362          if (backend.handlesEntry(mapEntry.getKey()))
363          {
364            iterator.remove();
365          }
366        }
367    
368        // Replace the ACI list with the copy.
369        aciList = aciCopy;
370      }
371    
372      /**
373       * Rename all ACIs under the specified old DN to the new DN. A simple
374       * interation over the entire list is performed.
375       * @param oldDN The DN of the original entry that was moved.
376       * @param newDN The DN of the new entry.
377       */
378      public synchronized void renameAci(DN oldDN, DN newDN ) {
379        LinkedHashMap<DN, List<Aci>> newCopyList =
380                new LinkedHashMap<DN, List<Aci>>();
381        int oldRDNCount=oldDN.getNumComponents();
382        int newRDNCount=newDN.getNumComponents();
383        for (Map.Entry<DN,List<Aci>> hashEntry : aciList.entrySet()) {
384          if(hashEntry.getKey().isDescendantOf(oldDN)) {
385            int keyRDNCount=hashEntry.getKey().getNumComponents();
386            int keepRDNCount=keyRDNCount - oldRDNCount;
387            RDN[] newRDNs = new RDN[keepRDNCount + newRDNCount];
388            for (int i=0; i < keepRDNCount; i++)
389              newRDNs[i] = hashEntry.getKey().getRDN(i);
390            for (int i=keepRDNCount, j=0; j < newRDNCount; i++,j++)
391              newRDNs[i] = newDN.getRDN(j);
392            DN relocateDN=new DN(newRDNs);
393            List<Aci> acis = new LinkedList<Aci>();
394            for(Aci aci : hashEntry.getValue()) {
395              try {
396                 Aci newAci =
397                   Aci.decode(ByteStringFactory.create(aci.toString()), relocateDN);
398                 acis.add(newAci);
399              } catch (AciException ex) {
400                //This should never happen since only a copy of the
401                //ACI with a new DN is being made. Log a message if it does and
402                //keep going.
403                Message message = WARN_ACI_ADD_LIST_FAILED_DECODE.get(
404                    aci.toString(), String.valueOf(relocateDN), ex.getMessage());
405                logError(message);
406              }
407            }
408            newCopyList.put(relocateDN, acis);
409          }  else
410            newCopyList.put(hashEntry.getKey(), hashEntry.getValue());
411        }
412        // Replace the ACI list with the copy.
413        aciList = newCopyList;
414      }
415    }