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 }