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