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 final 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 return candidates; 125 } 126 finally 127 { 128 lock.readLock().unlock(); 129 } 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 int addAci(List<? extends Entry> entries, 142 LinkedList<LocalizableMessage> failedACIMsgs) 143 { 144 lock.writeLock().lock(); 145 try 146 { 147 int validAcis = 0; 148 for (Entry entry : entries) { 149 DN dn=entry.getName(); 150 List<Attribute> attributeList = 151 entry.getOperationalAttribute(AciHandler.aciType); 152 validAcis += addAciAttributeList(aciList, dn, configDN, 153 attributeList, failedACIMsgs); 154 } 155 return validAcis; 156 } 157 finally 158 { 159 lock.writeLock().unlock(); 160 } 161 } 162 163 /** 164 * Add a set of ACIs to the ACI list. This is usually used a startup, when 165 * global ACIs are processed. 166 * 167 * @param dn The DN to add the ACIs under. 168 * 169 * @param acis A set of ACIs to add to the ACI list. 170 * 171 */ 172 public void addAci(DN dn, SortedSet<Aci> acis) { 173 lock.writeLock().lock(); 174 try 175 { 176 aciList.put(dn, new LinkedList<>(acis)); 177 } 178 finally 179 { 180 lock.writeLock().unlock(); 181 } 182 } 183 184 /** 185 * Add all of an entry's ACI (global or regular) attribute values to the 186 * ACI list. 187 * @param entry The entry containing the ACI attributes. 188 * @param hasAci True if the "aci" attribute type was seen in the entry. 189 * @param hasGlobalAci True if the "ds-cfg-global-aci" attribute type was 190 * seen in the entry. 191 * @param failedACIMsgs List that will hold error messages from ACI decode 192 * exceptions. 193 * @return The number of valid ACI attribute values added to the ACI list. 194 */ 195 public int addAci(Entry entry, boolean hasAci, 196 boolean hasGlobalAci, 197 List<LocalizableMessage> failedACIMsgs) { 198 lock.writeLock().lock(); 199 try 200 { 201 int validAcis = 0; 202 //Process global "ds-cfg-global-aci" attribute type. The oldentry 203 //DN is checked to verify it is equal to the config DN. If not those 204 //attributes are skipped. 205 if(hasGlobalAci && entry.getName().equals(configDN)) { 206 List<Attribute> attributeList = entry.getAttribute(globalAciType); 207 validAcis = addAciAttributeList(aciList, DN.rootDN(), configDN, 208 attributeList, failedACIMsgs); 209 } 210 211 if(hasAci) { 212 List<Attribute> attributeList = entry.getAttribute(aciType); 213 validAcis += addAciAttributeList(aciList, entry.getName(), configDN, 214 attributeList, failedACIMsgs); 215 } 216 return validAcis; 217 } 218 finally 219 { 220 lock.writeLock().unlock(); 221 } 222 } 223 224 /** 225 * Add an ACI's attribute type values to the ACI list. There is a chance that 226 * an ACI will throw an exception if it has an invalid syntax. If that 227 * happens a message will be logged and the ACI skipped. A count is 228 * returned of the number of valid ACIs added. 229 * @param aciList The ACI list to which the ACI is to be added. 230 * @param dn The DN to use as the key in the ACI list. 231 * @param configDN The DN of the configuration entry used to configure the 232 * ACI handler. Used if a global ACI has an decode exception. 233 * @param attributeList List of attributes containing the ACI attribute 234 * values. 235 * @param failedACIMsgs List that will hold error messages from ACI decode 236 * exceptions. 237 * @return The number of valid attribute values added to the ACI list. 238 */ 239 private static int addAciAttributeList(DITCacheMap<List<Aci>> aciList, 240 DN dn, DN configDN, 241 List<Attribute> attributeList, 242 List<LocalizableMessage> failedACIMsgs) { 243 if (attributeList.isEmpty()) { 244 return 0; 245 } 246 247 int validAcis=0; 248 List<Aci> acis = new ArrayList<>(); 249 for (Attribute attribute : attributeList) { 250 for (ByteString value : attribute) { 251 try { 252 acis.add(Aci.decode(value, dn)); 253 validAcis++; 254 } catch (AciException ex) { 255 DN msgDN=dn; 256 if(dn == DN.rootDN()) { 257 msgDN=configDN; 258 } 259 failedACIMsgs.add(WARN_ACI_ADD_LIST_FAILED_DECODE.get(value, msgDN, ex.getMessage())); 260 } 261 } 262 } 263 addAci(aciList, dn, acis); 264 return validAcis; 265 } 266 267 /** 268 * Remove all of the ACIs related to the old entry and then add all of the 269 * ACIs related to the new entry. This method locks/unlocks the list. 270 * In the case of global ACIs the DN of the entry is checked to make sure it 271 * is equal to the config DN. If not, the global ACI attribute type is 272 * silently skipped. 273 * @param oldEntry The old entry possibly containing old ACI attribute 274 * values. 275 * @param newEntry The new entry possibly containing new ACI attribute 276 * values. 277 * @param hasAci True if the "aci" attribute type was seen in the entry. 278 * @param hasGlobalAci True if the "ds-cfg-global-aci" attribute type was 279 * seen in the entry. 280 */ 281 public void modAciOldNewEntry(Entry oldEntry, Entry newEntry, 282 boolean hasAci, 283 boolean hasGlobalAci) { 284 285 lock.writeLock().lock(); 286 try 287 { 288 List<LocalizableMessage> failedACIMsgs=new LinkedList<>(); 289 //Process "aci" attribute types. 290 if(hasAci) { 291 aciList.remove(oldEntry.getName()); 292 List<Attribute> attributeList = 293 newEntry.getOperationalAttribute(aciType); 294 addAciAttributeList(aciList,newEntry.getName(), configDN, 295 attributeList, failedACIMsgs); 296 } 297 //Process global "ds-cfg-global-aci" attribute type. The oldentry 298 //DN is checked to verify it is equal to the config DN. If not those 299 //attributes are skipped. 300 if(hasGlobalAci && oldEntry.getName().equals(configDN)) { 301 aciList.remove(DN.rootDN()); 302 List<Attribute> attributeList = newEntry.getAttribute(globalAciType); 303 addAciAttributeList(aciList, DN.rootDN(), configDN, 304 attributeList, failedACIMsgs); 305 } 306 } 307 finally 308 { 309 lock.writeLock().unlock(); 310 } 311 } 312 313 /** 314 * Add ACI using the DN as a key. If the DN already 315 * has ACI(s) on the list, then the new ACI is added to the 316 * end of the array. 317 * @param aciList The set of ACIs to which ACI is to be added. 318 * @param dn The DN to use as the key. 319 * @param acis The ACI to be added. 320 */ 321 private static void addAci(DITCacheMap<List<Aci>> aciList, DN dn, 322 List<Aci> acis) 323 { 324 if(aciList.containsKey(dn)) { 325 List<Aci> tmpAci = aciList.get(dn); 326 tmpAci.addAll(acis); 327 } else { 328 aciList.put(dn, acis); 329 } 330 } 331 332 /** 333 * Remove global and regular ACIs from the list. It's possible that an entry 334 * could have both attribute types (aci and ds-cfg-global-aci). Global ACIs 335 * use the NULL DN for the key. In the case of global ACIs the DN of the 336 * entry is checked to make sure it is equal to the config DN. If not, the 337 * global ACI attribute type is silently skipped. 338 * @param entry The entry containing the global ACIs. 339 * @param hasAci True if the "aci" attribute type was seen in the entry. 340 * @param hasGlobalAci True if the "ds-cfg-global-aci" attribute type was 341 * seen in the entry. 342 * @return True if the ACI set was deleted. 343 */ 344 public boolean removeAci(Entry entry, boolean hasAci, 345 boolean hasGlobalAci) { 346 lock.writeLock().lock(); 347 try 348 { 349 DN entryDN = entry.getName(); 350 if (hasGlobalAci && entryDN.equals(configDN) && 351 aciList.remove(DN.rootDN()) == null) 352 { 353 return false; 354 } 355 if (hasAci || !hasGlobalAci) 356 { 357 return aciList.removeSubtree(entryDN, null); 358 } 359 return true; 360 } 361 finally 362 { 363 lock.writeLock().unlock(); 364 } 365 } 366 367 /** 368 * Remove all ACIs related to a backend. 369 * @param backend The backend to check if each DN is handled by that 370 * backend. 371 */ 372 public void removeAci(Backend<?> backend) { 373 374 lock.writeLock().lock(); 375 try 376 { 377 Iterator<Map.Entry<DN,List<Aci>>> iterator = 378 aciList.entrySet().iterator(); 379 while (iterator.hasNext()) 380 { 381 Map.Entry<DN,List<Aci>> mapEntry = iterator.next(); 382 if (backend.handlesEntry(mapEntry.getKey())) 383 { 384 iterator.remove(); 385 } 386 } 387 } 388 finally 389 { 390 lock.writeLock().unlock(); 391 } 392 } 393 394 /** 395 * Rename all ACIs under the specified old DN to the new DN. A simple 396 * interaction over the entire list is performed. 397 * @param oldDN The DN of the original entry that was moved. 398 * @param newDN The DN of the new entry. 399 */ 400 public void renameAci(DN oldDN, DN newDN ) { 401 402 lock.writeLock().lock(); 403 try 404 { 405 Map<DN,List<Aci>> tempAciList = new HashMap<>(); 406 Iterator<Map.Entry<DN,List<Aci>>> iterator = 407 aciList.entrySet().iterator(); 408 while (iterator.hasNext()) { 409 Map.Entry<DN,List<Aci>> hashEntry = iterator.next(); 410 DN keyDn = hashEntry.getKey(); 411 if (keyDn.isSubordinateOrEqualTo(oldDN)) { 412 DN relocateDN = keyDn.rename(oldDN, newDN); 413 List<Aci> acis = new LinkedList<>(); 414 for(Aci aci : hashEntry.getValue()) { 415 try { 416 Aci newAci = 417 Aci.decode(ByteString.valueOfUtf8(aci.toString()), relocateDN); 418 acis.add(newAci); 419 } catch (AciException ex) { 420 //This should never happen since only a copy of the 421 //ACI with a new DN is being made. Log a message if it does and 422 //keep going. 423 logger.warn(WARN_ACI_ADD_LIST_FAILED_DECODE, aci, relocateDN, ex.getMessage()); 424 } 425 } 426 tempAciList.put(relocateDN, acis); 427 iterator.remove(); 428 } 429 } 430 aciList.putAll(tempAciList); 431 } 432 finally 433 { 434 lock.writeLock().unlock(); 435 } 436 } 437}