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 }