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}