/* * 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 legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * 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 legal-notices/CDDLv1_0.txt. * 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 * * * Copyright 2008-2010 Sun Microsystems, Inc. * Portions Copyright 2011-2014 ForgeRock AS */ package org.opends.server.core; import java.util.HashSet; import java.util.Set; import org.forgerock.i18n.LocalizableMessage; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.opends.server.api.ChangeNotificationListener; import org.opends.server.api.ClientConnection; import org.opends.server.api.DITCacheMap; import org.forgerock.i18n.slf4j.LocalizedLogger; import org.opends.server.types.DisconnectReason; import org.opends.server.types.DN; import org.opends.server.types.Entry; import org.opends.server.types.operation.PostResponseAddOperation; import org.opends.server.types.operation.PostResponseDeleteOperation; import org.opends.server.types.operation.PostResponseModifyOperation; import org.opends.server.types.operation.PostResponseModifyDNOperation; import static org.opends.messages.CoreMessages.*; /** * This class provides a data structure which maps an authenticated user DN to * the set of client connections authenticated as that user. Note that a single * client connection may be registered with two different user DNs if the client * has different authentication and authorization identities. *

* This class also provides a mechanism for detecting changes to authenticated * user entries and notifying the corresponding client connections so that they * can update their cached versions. */ public class AuthenticatedUsers implements ChangeNotificationListener { private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); // The mapping between authenticated user DNs and the associated client // connection objects. private DITCacheMap> userMap; // Lock to protect internal data structures. private final ReentrantReadWriteLock lock; /** * Creates a new instance of this authenticated users object. */ public AuthenticatedUsers() { userMap = new DITCacheMap>(); lock = new ReentrantReadWriteLock(); DirectoryServer.registerChangeNotificationListener(this); } /** * Registers the provided user DN and client connection with this object. * * @param userDN The DN of the user associated with the provided * client connection. * @param clientConnection The client connection over which the user is * authenticated. */ public void put(DN userDN, ClientConnection clientConnection) { lock.writeLock().lock(); try { CopyOnWriteArraySet connectionSet = userMap.get(userDN); if (connectionSet == null) { connectionSet = new CopyOnWriteArraySet(); connectionSet.add(clientConnection); userMap.put(userDN, connectionSet); } else { connectionSet.add(clientConnection); } } finally { lock.writeLock().unlock(); } } /** * Deregisters the provided user DN and client connection with this object. * * @param userDN The DN of the user associated with the provided * client connection. * @param clientConnection The client connection over which the user is * authenticated. */ public void remove(DN userDN, ClientConnection clientConnection) { lock.writeLock().lock(); try { CopyOnWriteArraySet connectionSet = userMap.get(userDN); if (connectionSet != null) { connectionSet.remove(clientConnection); if (connectionSet.isEmpty()) { userMap.remove(userDN); } } } finally { lock.writeLock().unlock(); } } /** * Retrieves the set of client connections authenticated as the specified * user. This method is only intended for internal testing use and should not * be called for any other purpose. * * @param userDN The DN of the user for which to retrieve the corresponding * set of client connections. * * @return The set of client connections authenticated as the specified user, * or {@code null} if there are none. */ public CopyOnWriteArraySet get(DN userDN) { lock.readLock().lock(); try { return userMap.get(userDN); } finally { lock.readLock().unlock(); } } /** * Performs any processing that may be required after an add * operation. * * @param addOperation The add operation that was performed in the * server. * @param entry The entry that was added to the server. */ public void handleAddOperation( PostResponseAddOperation addOperation, Entry entry) { // No implementation is required for add operations, since a connection // can't be authenticated as a user that doesn't exist yet. } /** * Performs any processing that may be required after a delete * operation. * * @param deleteOperation The delete operation that was performed * in the server. * @param entry The entry that was removed from the * server. */ public void handleDeleteOperation( PostResponseDeleteOperation deleteOperation, Entry entry) { // Identify any client connections that may be authenticated // or authorized as the user whose entry has been deleted and // terminate them. Set> arraySet = new HashSet>(); lock.writeLock().lock(); try { userMap.removeSubtree(entry.getName(), arraySet); } finally { lock.writeLock().unlock(); } for (CopyOnWriteArraySet connectionSet : arraySet) { for (ClientConnection conn : connectionSet) { LocalizableMessage message = WARN_CLIENTCONNECTION_DISCONNECT_DUE_TO_DELETE.get( entry.getName()); conn.disconnect(DisconnectReason.INVALID_CREDENTIALS, true, message); } } } /** * Performs any processing that may be required after a modify * operation. * * @param modifyOperation The modify operation that was performed * in the server. * @param oldEntry The entry before it was updated. * @param newEntry The entry after it was updated. */ public void handleModifyOperation( PostResponseModifyOperation modifyOperation, Entry oldEntry, Entry newEntry) { // Identify any client connections that may be authenticated // or authorized as the user whose entry has been modified // and update them with the latest version of the entry. lock.writeLock().lock(); try { CopyOnWriteArraySet connectionSet = userMap.get(oldEntry.getName()); if (connectionSet != null) { for (ClientConnection conn : connectionSet) { conn.updateAuthenticationInfo(oldEntry, newEntry); } } } finally { lock.writeLock().unlock(); } } /** * Performs any processing that may be required after a modify DN * operation. * * @param modifyDNOperation The modify DN operation that was * performed in the server. * @param oldEntry The entry before it was updated. * @param newEntry The entry after it was updated. */ public void handleModifyDNOperation( PostResponseModifyDNOperation modifyDNOperation, Entry oldEntry, Entry newEntry) { String oldDNString = oldEntry.getName().toNormalizedString(); String newDNString = newEntry.getName().toNormalizedString(); // Identify any client connections that may be authenticated // or authorized as the user whose entry has been modified // and update them with the latest version of the entry. lock.writeLock().lock(); try { Set> arraySet = new HashSet>(); userMap.removeSubtree(oldEntry.getName(), arraySet); for (CopyOnWriteArraySet connectionSet : arraySet) { DN authNDN = null; DN authZDN = null; DN newAuthNDN = null; DN newAuthZDN = null; CopyOnWriteArraySet newAuthNSet = null; CopyOnWriteArraySet newAuthZSet = null; for (ClientConnection conn : connectionSet) { if (authNDN == null) { authNDN = conn.getAuthenticationInfo().getAuthenticationDN(); try { StringBuilder builder = new StringBuilder( authNDN.toNormalizedString()); int oldDNIndex = builder.lastIndexOf(oldDNString); builder.replace(oldDNIndex, builder.length(), newDNString); String newAuthNDNString = builder.toString(); newAuthNDN = DN.valueOf(newAuthNDNString); } catch (Exception e) { // Shouldnt happen. logger.traceException(e); } } if (authZDN == null) { authZDN = conn.getAuthenticationInfo().getAuthorizationDN(); try { StringBuilder builder = new StringBuilder( authZDN.toNormalizedString()); int oldDNIndex = builder.lastIndexOf(oldDNString); builder.replace(oldDNIndex, builder.length(), newDNString); String newAuthZDNString = builder.toString(); newAuthZDN = DN.valueOf(newAuthZDNString); } catch (Exception e) { // Shouldnt happen. logger.traceException(e); } } if ((newAuthNDN != null) && (authNDN != null) && authNDN.isDescendantOf(oldEntry.getName())) { if (newAuthNSet == null) { newAuthNSet = new CopyOnWriteArraySet(); } conn.getAuthenticationInfo().setAuthenticationDN(newAuthNDN); newAuthNSet.add(conn); } if ((newAuthZDN != null) && (authZDN != null) && authZDN.isDescendantOf(oldEntry.getName())) { if (newAuthZSet == null) { newAuthZSet = new CopyOnWriteArraySet(); } conn.getAuthenticationInfo().setAuthorizationDN(newAuthZDN); newAuthZSet.add(conn); } } if ((newAuthNDN != null) && (newAuthNSet != null)) { userMap.put(newAuthNDN, newAuthNSet); } if ((newAuthZDN != null) && (newAuthZSet != null)) { userMap.put(newAuthZDN, newAuthZSet); } } } finally { lock.writeLock().unlock(); } } }