001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2008-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2013-2014 ForgeRock AS
026 */
027package org.opends.server.authorization.dseecompat;
028
029import static org.opends.messages.AccessControlMessages.*;
030import org.forgerock.i18n.slf4j.LocalizedLogger;
031import static org.opends.server.authorization.dseecompat.AciHandler.*;
032import java.util.*;
033import java.util.concurrent.locks.ReentrantReadWriteLock;
034
035import org.forgerock.i18n.LocalizableMessage;
036import org.opends.server.api.Backend;
037import org.opends.server.api.DITCacheMap;
038import org.opends.server.types.*;
039import org.forgerock.opendj.ldap.ByteString;
040
041/**
042 * The AciList class performs caching of the ACI attribute values
043 * using the entry DN as the key.
044 */
045public class AciList {
046
047  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
048
049
050  /**
051   * A map containing all the ACIs.
052   * We use the copy-on-write technique to avoid locking when reading.
053   */
054  private volatile DITCacheMap<List<Aci>> aciList = new DITCacheMap<>();
055
056  /**
057   * Lock to protect internal data structures.
058   */
059  private final ReentrantReadWriteLock lock =
060          new ReentrantReadWriteLock();
061
062  /** The configuration DN used to compare against the global ACI entry DN. */
063  private DN configDN;
064
065  /**
066   * Constructor to create an ACI list to cache ACI attribute types.
067   * @param configDN The configuration entry DN.
068   */
069  public AciList(DN configDN) {
070     this.configDN=configDN;
071  }
072
073  /**
074   * Using the base DN, return a list of ACIs that are candidates for
075   * evaluation by walking up from the base DN towards the root of the
076   * DIT gathering ACIs on parents. Global ACIs use the NULL DN as the key
077   * and are included in the candidate set only if they have no
078   * "target" keyword rules, or if the target keyword rule matches for
079   * the specified base DN.
080   *
081   * @param baseDN  The DN to check.
082   * @return A list of candidate ACIs that might be applicable.
083   */
084  public List<Aci> getCandidateAcis(DN baseDN) {
085    List<Aci> candidates = new LinkedList<>();
086    if(baseDN == null)
087    {
088      return candidates;
089    }
090
091    lock.readLock().lock();
092    try
093    {
094      //Save the baseDN in case we need to evaluate a global ACI.
095      DN entryDN=baseDN;
096      while (baseDN != null) {
097        List<Aci> acis = aciList.get(baseDN);
098        if (acis != null) {
099          //Check if there are global ACIs. Global ACI has a NULL DN.
100          if (baseDN.isRootDN()) {
101            for (Aci aci : acis) {
102              AciTargets targets = aci.getTargets();
103              //If there is a target, evaluate it to see if this ACI should
104              //be included in the candidate set.
105              if (targets != null
106                  && AciTargets.isTargetApplicable(aci, targets, entryDN))
107              {
108                  candidates.add(aci);  //Add this ACI to the candidates.
109              }
110            }
111          } else {
112            candidates.addAll(acis);
113          }
114        }
115        if(baseDN.isRootDN()) {
116          break;
117        }
118        DN parentDN=baseDN.parent();
119        if(parentDN == null) {
120          baseDN=DN.rootDN();
121        } else {
122          baseDN=parentDN;
123        }
124      }
125    }
126    finally
127    {
128      lock.readLock().unlock();
129    }
130
131    return candidates;
132  }
133
134  /**
135   * Add all the ACI from a set of entries to the ACI list. There is no need
136   * to check for global ACIs since they are processe by the AciHandler at
137   * startup using the addACi single entry method.
138   * @param entries The set of entries containing the "aci" attribute values.
139   * @param failedACIMsgs List that will hold error messages from ACI decode
140   *                      exceptions.
141   * @return The number of valid ACI attribute values added to the ACI list.
142   */
143  public int addAci(List<? extends Entry> entries,
144                                 LinkedList<LocalizableMessage> failedACIMsgs)
145  {
146    int validAcis=0;
147
148    lock.writeLock().lock();
149    try
150    {
151      for (Entry entry : entries) {
152        DN dn=entry.getName();
153        List<Attribute> attributeList =
154             entry.getOperationalAttribute(AciHandler.aciType);
155        validAcis += addAciAttributeList(aciList, dn, configDN,
156                                         attributeList, failedACIMsgs);
157      }
158    }
159    finally
160    {
161      lock.writeLock().unlock();
162    }
163
164    return validAcis;
165  }
166
167  /**
168   * Add a set of ACIs to the ACI list. This is usually used a startup, when
169   * global ACIs are processed.
170   *
171   * @param dn The DN to add the ACIs under.
172   *
173   * @param acis A set of ACIs to add to the ACI list.
174   *
175   */
176  public void addAci(DN dn, SortedSet<Aci> acis) {
177    lock.writeLock().lock();
178    try
179    {
180      aciList.put(dn, new LinkedList<>(acis));
181    }
182    finally
183    {
184      lock.writeLock().unlock();
185    }
186  }
187
188  /**
189   * Add all of an entry's ACI (global or regular) attribute values to the
190   * ACI list.
191   * @param entry The entry containing the ACI attributes.
192   * @param hasAci True if the "aci" attribute type was seen in the entry.
193   * @param hasGlobalAci True if the "ds-cfg-global-aci" attribute type was
194   * seen in the entry.
195   * @param failedACIMsgs List that will hold error messages from ACI decode
196   *                      exceptions.
197   * @return The number of valid ACI attribute values added to the ACI list.
198   */
199  public int addAci(Entry entry, boolean hasAci,
200                                 boolean hasGlobalAci,
201                                 List<LocalizableMessage> failedACIMsgs) {
202    int validAcis=0;
203
204    lock.writeLock().lock();
205    try
206    {
207      //Process global "ds-cfg-global-aci" attribute type. The oldentry
208      //DN is checked to verify it is equal to the config DN. If not those
209      //attributes are skipped.
210      if(hasGlobalAci && entry.getName().equals(configDN)) {
211          List<Attribute> attributeList = entry.getAttribute(globalAciType);
212          validAcis = addAciAttributeList(aciList, DN.rootDN(), configDN,
213                                          attributeList, failedACIMsgs);
214      }
215
216      if(hasAci) {
217          List<Attribute> attributeList = entry.getAttribute(aciType);
218          validAcis += addAciAttributeList(aciList, entry.getName(), configDN,
219                                           attributeList, failedACIMsgs);
220      }
221    }
222    finally
223    {
224      lock.writeLock().unlock();
225    }
226
227    return validAcis;
228  }
229
230  /**
231   * Add an ACI's attribute type values to the ACI list. There is a chance that
232   * an ACI will throw an exception if it has an invalid syntax. If that
233   * happens a message will be logged and the ACI skipped.  A count is
234   * returned of the number of valid ACIs added.
235   * @param aciList The ACI list to which the ACI is to be added.
236   * @param dn The DN to use as the key in the ACI list.
237   * @param configDN The DN of the configuration entry used to configure the
238   *                 ACI handler. Used if a global ACI has an decode exception.
239   * @param attributeList List of attributes containing the ACI attribute
240   * values.
241   * @param failedACIMsgs List that will hold error messages from ACI decode
242   *                      exceptions.
243   * @return The number of valid attribute values added to the ACI list.
244   */
245  private static int addAciAttributeList(DITCacheMap<List<Aci>> aciList,
246                                         DN dn, DN configDN,
247                                         List<Attribute> attributeList,
248                                         List<LocalizableMessage> failedACIMsgs) {
249
250    if (attributeList == null) {
251      return 0;
252    }
253
254    int validAcis=0;
255    List<Aci> acis = new ArrayList<>();
256    for (Attribute attribute : attributeList) {
257      for (ByteString value : attribute) {
258        try {
259          acis.add(Aci.decode(value, dn));
260          validAcis++;
261        } catch (AciException ex) {
262          DN msgDN=dn;
263          if(dn == DN.rootDN()) {
264            msgDN=configDN;
265          }
266          failedACIMsgs.add(WARN_ACI_ADD_LIST_FAILED_DECODE.get(value, msgDN, ex.getMessage()));
267        }
268      }
269    }
270    addAci(aciList, dn, acis);
271    return validAcis;
272  }
273
274  /**
275   * Remove all of the ACIs related to the old entry and then add all of the
276   * ACIs related to the new entry. This method locks/unlocks the list.
277   * In the case of global ACIs the DN of the entry is checked to make sure it
278   * is equal to the config DN. If not, the global ACI attribute type is
279   * silently skipped.
280   * @param oldEntry The old entry possibly containing old ACI attribute
281   * values.
282   * @param newEntry The new entry possibly containing new ACI attribute
283   * values.
284   * @param hasAci True if the "aci" attribute type was seen in the entry.
285   * @param hasGlobalAci True if the "ds-cfg-global-aci" attribute type was
286   * seen in the entry.
287   */
288  public void modAciOldNewEntry(Entry oldEntry, Entry newEntry,
289                                             boolean hasAci,
290                                             boolean hasGlobalAci) {
291
292    lock.writeLock().lock();
293    try
294    {
295      List<LocalizableMessage> failedACIMsgs=new LinkedList<>();
296      //Process "aci" attribute types.
297      if(hasAci) {
298          aciList.remove(oldEntry.getName());
299          List<Attribute> attributeList =
300                  newEntry.getOperationalAttribute(aciType);
301          addAciAttributeList(aciList,newEntry.getName(), configDN,
302                              attributeList, failedACIMsgs);
303      }
304      //Process global "ds-cfg-global-aci" attribute type. The oldentry
305      //DN is checked to verify it is equal to the config DN. If not those
306      //attributes are skipped.
307      if(hasGlobalAci && oldEntry.getName().equals(configDN)) {
308          aciList.remove(DN.rootDN());
309          List<Attribute> attributeList =
310                  newEntry.getAttribute(globalAciType);
311          addAciAttributeList(aciList, DN.rootDN(), configDN,
312                              attributeList, failedACIMsgs);
313      }
314    }
315    finally
316    {
317      lock.writeLock().unlock();
318    }
319  }
320
321  /**
322   * Add ACI using the DN as a key. If the DN already
323   * has ACI(s) on the list, then the new ACI is added to the
324   * end of the array.
325   * @param aciList The set of ACIs to which ACI is to be added.
326   * @param dn The DN to use as the key.
327   * @param acis The ACI to be added.
328   */
329  private static void addAci(DITCacheMap<List<Aci>> aciList, DN dn,
330                             List<Aci> acis)
331  {
332    if(aciList.containsKey(dn)) {
333      List<Aci> tmpAci = aciList.get(dn);
334      tmpAci.addAll(acis);
335    } else {
336      aciList.put(dn, acis);
337    }
338  }
339
340  /**
341   * Remove global and regular ACIs from the list. It's possible that an entry
342   * could have both attribute types (aci and ds-cfg-global-aci). Global ACIs
343   * use the NULL DN for the key.  In the case of global ACIs the DN of the
344   * entry is checked to make sure it is equal to the config DN. If not, the
345   * global ACI attribute type is silently skipped.
346   * @param entry The entry containing the global ACIs.
347   * @param hasAci True if the "aci" attribute type was seen in the entry.
348   * @param hasGlobalAci True if the "ds-cfg-global-aci" attribute type was
349   * seen in the entry.
350   * @return  True if the ACI set was deleted.
351   */
352  public boolean removeAci(Entry entry,  boolean hasAci,
353                                                      boolean hasGlobalAci) {
354    DN entryDN = entry.getName();
355
356    lock.writeLock().lock();
357    try
358    {
359      if (hasGlobalAci && entryDN.equals(configDN) &&
360          aciList.remove(DN.rootDN()) == null)
361      {
362        return false;
363      }
364      if (hasAci || !hasGlobalAci)
365      {
366        return aciList.removeSubtree(entryDN, null);
367      }
368    }
369    finally
370    {
371      lock.writeLock().unlock();
372    }
373
374    return true;
375  }
376
377  /**
378   * Remove all ACIs related to a backend.
379   * @param backend  The backend to check if each DN is handled by that
380   * backend.
381   */
382  public void removeAci(Backend<?> backend) {
383
384    lock.writeLock().lock();
385    try
386    {
387      Iterator<Map.Entry<DN,List<Aci>>> iterator =
388              aciList.entrySet().iterator();
389      while (iterator.hasNext())
390      {
391        Map.Entry<DN,List<Aci>> mapEntry = iterator.next();
392        if (backend.handlesEntry(mapEntry.getKey()))
393        {
394          iterator.remove();
395        }
396      }
397    }
398    finally
399    {
400      lock.writeLock().unlock();
401    }
402  }
403
404  /**
405   * Rename all ACIs under the specified old DN to the new DN. A simple
406   * interaction over the entire list is performed.
407   * @param oldDN The DN of the original entry that was moved.
408   * @param newDN The DN of the new entry.
409   */
410  public void renameAci(DN oldDN, DN newDN ) {
411
412    int oldRDNCount=oldDN.size();
413    int newRDNCount=newDN.size();
414
415    lock.writeLock().lock();
416    try
417    {
418      Map<DN,List<Aci>> tempAciList = new HashMap<>();
419      Iterator<Map.Entry<DN,List<Aci>>> iterator =
420              aciList.entrySet().iterator();
421      while (iterator.hasNext()) {
422        Map.Entry<DN,List<Aci>> hashEntry = iterator.next();
423        if(hashEntry.getKey().isDescendantOf(oldDN)) {
424          int keyRDNCount=hashEntry.getKey().size();
425          int keepRDNCount=keyRDNCount - oldRDNCount;
426          RDN[] newRDNs = new RDN[keepRDNCount + newRDNCount];
427          for (int i=0; i < keepRDNCount; i++) {
428            newRDNs[i] = hashEntry.getKey().getRDN(i);
429          }
430          for (int i=keepRDNCount, j=0; j < newRDNCount; i++,j++) {
431            newRDNs[i] = newDN.getRDN(j);
432          }
433          DN relocateDN=new DN(newRDNs);
434          List<Aci> acis = new LinkedList<>();
435          for(Aci aci : hashEntry.getValue()) {
436            try {
437               Aci newAci =
438                 Aci.decode(ByteString.valueOf(aci.toString()), relocateDN);
439               acis.add(newAci);
440            } catch (AciException ex) {
441              //This should never happen since only a copy of the
442              //ACI with a new DN is being made. Log a message if it does and
443              //keep going.
444              logger.warn(WARN_ACI_ADD_LIST_FAILED_DECODE, aci, relocateDN, ex.getMessage());
445            }
446          }
447          tempAciList.put(relocateDN, acis);
448          iterator.remove();
449        }
450      }
451      aciList.putAll(tempAciList);
452    }
453    finally
454    {
455      lock.writeLock().unlock();
456    }
457  }
458}