/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Portions Copyright 2007 Sun Microsystems, Inc. */ package org.opends.server.authorization.dseecompat; import static org.opends.server.authorization.dseecompat.AciMessages.*; import static org.opends.server.loggers.Error.logError; import static org.opends.server.messages.MessageHandler.getMessage; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.opends.server.types.Attribute; import org.opends.server.types.AttributeValue; import org.opends.server.types.DN; import org.opends.server.types.Entry; import org.opends.server.types.ErrorLogCategory; import org.opends.server.types.ErrorLogSeverity; import org.opends.server.api.Backend; /** * The AciList class performs caching of the ACI attribute values * using the entry DN as the key. */ public class AciList { /* * TODO Change linked list implementation as suggested below. * I would strongly recommend that you change aciList to be * LinkedHashMap> or LinkedHashMap rather than * LinkedHashMap. It looks like there are some costly * string->DN and even string->DN->string conversions. Further, the very * hackish way that the linked-list is currently maintained is very * ugly and potentially error-prone. */ private LinkedHashMap aciList = new LinkedHashMap(); /* * TODO Evaluate making this class lock-free. * I would definitely try to make this a lock-free class if at all * possible. Read locks aren't free to acquire, since they still require * an exclusive lock at some point. If possible, you should use a * copy-on-write structure so that you only incur penalties for changing * the ACI list (which should be a rare event) and there is no need for * any kind of locking at all for read operations. */ private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private final Lock aciReadLock = rwl.readLock(); private final Lock aciWriteLock = rwl.writeLock(); /* * TODO Add support global ACIs in config.ldif. * */ /** * Using the base DN, return a list of ACIs that are candidates for * evaluation by walking up from the base DN towards the root of the * DIT gathering ACIs on parents. * * @param baseDN The DN to check. * @return A list of candidate ACIs that might be applicable. */ public LinkedList getCandidateAcis(DN baseDN) { LinkedList candidates = new LinkedList(); if(baseDN == null) return candidates; try { aciReadLock.lock(); while(baseDN != null) { Aci aci = aciList.get(baseDN); if (aci != null) { while (aci != null) { candidates.add(aci); aci = aci.next; } } if(baseDN.isNullDN()) break; DN parentDN=baseDN.getParent(); if(parentDN == null) baseDN=DN.nullDN(); else baseDN=parentDN; } } finally { aciReadLock.unlock(); } return candidates; } /** * Add all of an entries ACI attribute values to the ACI list. This * method locks/unlocks the list. * @param entry The entry containing the "aci" attribute values.\ * @return The number of valid ACI attribute values added to the ACI list. */ public int addAci(Entry entry) { int validAcis=0; DN dn=entry.getDN(); List attributeList = entry.getOperationalAttribute(AciHandler.aciType); try { aciWriteLock.lock(); validAcis=addAciAttributeListNoLock(dn, attributeList); } finally { aciWriteLock.unlock(); } return validAcis; } /** * Add "aci" attribute type values to the ACI list. There is a chance * that an ACI will throw an exception if it has an invalid syntax. * If that happens a message will be logged and the ACI skipped. * @param dn The DN to use a the key in the ACI list. * @param attributeList List of attributes contain the "aci" attribute * values. * @return The number of valid "aci" attribute types added to the ACI list. */ private int addAciAttributeListNoLock(DN dn, List attributeList) { int validAcis=0; for (Attribute attribute : attributeList) { for (AttributeValue value : attribute.getValues()) { try { Aci aci= Aci.decode(value.getValue(),dn); addAci(dn, aci); validAcis++; } catch (AciException ex) { /* An illegal ACI might have been loaded * during import and is failing at ACI handler * initialization time. Log a message and continue * processing. ACIs added via LDAP add have their * syntax checked before adding and should never * hit this code. */ int msgID = MSGID_ACI_ADD_LIST_FAILED_DECODE; String message = getMessage(msgID, ex.getMessage()); logError(ErrorLogCategory.ACCESS_CONTROL, ErrorLogSeverity.SEVERE_WARNING, message, msgID); } } } return validAcis; } /** * Remove all of the ACIs related to the old entry and then add all of the * ACIs related to the new entry. This method locks/unlocks the list. * @param oldEntry The old entry maybe containing old "aci" attribute * values. * @param newEntry The new entry maybe containing new "aci" attribute * values. */ public void modAciOldNewEntry(Entry oldEntry, Entry newEntry) { if((oldEntry.hasOperationalAttribute(AciHandler.aciType)) || (newEntry.hasOperationalAttribute(AciHandler.aciType))) { try { aciWriteLock.lock(); aciList.remove(oldEntry.getDN()); List attributeList = newEntry.getOperationalAttribute(AciHandler.aciType, null); addAciAttributeListNoLock(newEntry.getDN(),attributeList); } finally { aciWriteLock.unlock(); } } } /** * Add an ACI using the DN as a key. If the DN already * has ACI(s) on the list, then the new ACI is added to the * end of the linked list. * @param dn The DN to use as the key. * @param aci The ACI to add to the list. */ public void addAci(DN dn, Aci aci) { if(aciList.containsKey(dn)) { Aci tmpAci = aciList.get(dn); while(tmpAci.next != null) tmpAci=tmpAci.next; tmpAci.next=aci; } else aciList.put(dn, aci); } /** * Remove ACIs related to an entry. * @param entry The entry to be removed. * @return True if the ACI set was deleted. */ public boolean removeAci(Entry entry) { boolean deleted = false; try { aciWriteLock.lock(); if (aciList.remove(entry.getDN()) != null) deleted = true; } finally { aciWriteLock.unlock(); } return deleted; } /** * Remove all ACIs related to a backend. * @param backend The backend to check if each DN is handled by that * backend. */ public void removeAci (Backend backend) { try { aciWriteLock.lock(); Set keys=aciList.keySet(); for(DN dn : keys) { if (backend.handlesEntry(dn)) aciList.remove(dn); } } finally { aciWriteLock.unlock(); } } }