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 2011-2015 ForgeRock AS
026 */
027package org.opends.server.core;
028
029import java.util.EnumSet;
030import java.util.HashSet;
031import java.util.Set;
032import java.util.concurrent.CopyOnWriteArraySet;
033import java.util.concurrent.locks.ReentrantReadWriteLock;
034
035import org.forgerock.i18n.LocalizableMessage;
036import org.forgerock.i18n.slf4j.LocalizedLogger;
037import org.forgerock.opendj.ldap.ResultCode;
038import org.opends.server.api.ClientConnection;
039import org.opends.server.api.DITCacheMap;
040import org.opends.server.api.plugin.InternalDirectoryServerPlugin;
041import org.opends.server.api.plugin.PluginResult.PostResponse;
042import org.opends.server.types.DN;
043import org.opends.server.types.DirectoryException;
044import org.opends.server.types.DisconnectReason;
045import org.opends.server.types.Entry;
046import org.opends.server.types.operation.PostResponseDeleteOperation;
047import org.opends.server.types.operation.PostResponseModifyDNOperation;
048import org.opends.server.types.operation.PostResponseModifyOperation;
049
050import static org.opends.messages.CoreMessages.*;
051import static org.opends.server.api.plugin.PluginType.*;
052
053/**
054 * This class provides a data structure which maps an authenticated user DN to
055 * the set of client connections authenticated as that user.  Note that a single
056 * client connection may be registered with two different user DNs if the client
057 * has different authentication and authorization identities.
058 * <BR><BR>
059 * This class also provides a mechanism for detecting changes to authenticated
060 * user entries and notifying the corresponding client connections so that they
061 * can update their cached versions.
062 */
063public class AuthenticatedUsers extends InternalDirectoryServerPlugin
064{
065  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
066
067  /**
068   * The mapping between authenticated user DNs and the associated client
069   * connection objects.
070   */
071  private final DITCacheMap<CopyOnWriteArraySet<ClientConnection>> userMap;
072
073  /** Lock to protect internal data structures. */
074  private final ReentrantReadWriteLock lock;
075
076  /** Dummy configuration DN. */
077  private static final String CONFIG_DN = "cn=Authenticated Users,cn=config";
078
079  /**
080   * Creates a new instance of this authenticated users object.
081   */
082  public AuthenticatedUsers()
083  {
084    super(toDN(CONFIG_DN), EnumSet.of(
085        // No implementation is required for add operations, since a connection
086        // can not be authenticated as a user that does not exist yet.
087        POST_RESPONSE_MODIFY, POST_RESPONSE_MODIFY_DN, POST_RESPONSE_DELETE),
088        true);
089    userMap = new DITCacheMap<>();
090    lock = new ReentrantReadWriteLock();
091
092    DirectoryServer.registerInternalPlugin(this);
093  }
094
095  private static DN toDN(String dn)
096  {
097    try
098    {
099      return DN.valueOf(dn);
100    }
101    catch (DirectoryException e)
102    {
103      throw new RuntimeException(e);
104    }
105  }
106
107  /**
108   * Registers the provided user DN and client connection with this object.
109   *
110   * @param  userDN            The DN of the user associated with the provided
111   *                           client connection.
112   * @param  clientConnection  The client connection over which the user is
113   *                           authenticated.
114   */
115  public void put(DN userDN, ClientConnection clientConnection)
116  {
117    lock.writeLock().lock();
118    try
119    {
120      CopyOnWriteArraySet<ClientConnection> connectionSet = userMap.get(userDN);
121      if (connectionSet == null)
122      {
123        connectionSet = new CopyOnWriteArraySet<>();
124        connectionSet.add(clientConnection);
125        userMap.put(userDN, connectionSet);
126      }
127      else
128      {
129        connectionSet.add(clientConnection);
130      }
131    }
132    finally
133    {
134      lock.writeLock().unlock();
135    }
136  }
137
138
139
140  /**
141   * Deregisters the provided user DN and client connection with this object.
142   *
143   * @param  userDN            The DN of the user associated with the provided
144   *                           client connection.
145   * @param  clientConnection  The client connection over which the user is
146   *                           authenticated.
147   */
148  public void remove(DN userDN, ClientConnection clientConnection)
149  {
150    lock.writeLock().lock();
151    try
152    {
153      CopyOnWriteArraySet<ClientConnection> connectionSet = userMap.get(userDN);
154      if (connectionSet != null)
155      {
156        connectionSet.remove(clientConnection);
157        if (connectionSet.isEmpty())
158        {
159          userMap.remove(userDN);
160        }
161      }
162    }
163    finally
164    {
165      lock.writeLock().unlock();
166    }
167  }
168
169
170
171  /**
172   * Retrieves the set of client connections authenticated as the specified
173   * user.  This method is only intended for internal testing use and should not
174   * be called for any other purpose.
175   *
176   * @param  userDN  The DN of the user for which to retrieve the corresponding
177   *                 set of client connections.
178   *
179   * @return  The set of client connections authenticated as the specified user,
180   *          or {@code null} if there are none.
181   */
182  public CopyOnWriteArraySet<ClientConnection> get(DN userDN)
183  {
184    lock.readLock().lock();
185    try
186    {
187      return userMap.get(userDN);
188    }
189    finally
190    {
191      lock.readLock().unlock();
192    }
193  }
194
195  /** {@inheritDoc} */
196  @Override
197  public PostResponse doPostResponse(PostResponseDeleteOperation op)
198  {
199    if (op.getResultCode() != ResultCode.SUCCESS) {
200      return PostResponse.continueOperationProcessing();
201    }
202
203    final DN entryDN = op.getEntryDN();
204    // Identify any client connections that may be authenticated
205    // or authorized as the user whose entry has been deleted and terminate them
206    Set<CopyOnWriteArraySet<ClientConnection>> arraySet = new HashSet<>();
207    lock.writeLock().lock();
208    try
209    {
210      userMap.removeSubtree(entryDN, arraySet);
211    }
212    finally
213    {
214      lock.writeLock().unlock();
215    }
216
217    for (CopyOnWriteArraySet<ClientConnection> connectionSet : arraySet)
218    {
219      for (ClientConnection conn : connectionSet)
220      {
221        LocalizableMessage message = WARN_CLIENTCONNECTION_DISCONNECT_DUE_TO_DELETE.get(entryDN);
222        conn.disconnect(DisconnectReason.INVALID_CREDENTIALS, true, message);
223      }
224    }
225    return PostResponse.continueOperationProcessing();
226  }
227
228  /** {@inheritDoc} */
229  @Override
230  public PostResponse doPostResponse(PostResponseModifyOperation op)
231  {
232    final Entry oldEntry = op.getCurrentEntry();
233
234    if (op.getResultCode() != ResultCode.SUCCESS || oldEntry == null) {
235      return PostResponse.continueOperationProcessing();
236    }
237
238    final Entry newEntry = op.getModifiedEntry();
239    // Identify any client connections that may be authenticated
240    // or authorized as the user whose entry has been modified
241    // and update them with the latest version of the entry.
242    lock.writeLock().lock();
243    try
244    {
245      CopyOnWriteArraySet<ClientConnection> connectionSet = userMap.get(oldEntry.getName());
246      if (connectionSet != null)
247      {
248        for (ClientConnection conn : connectionSet)
249        {
250          conn.updateAuthenticationInfo(oldEntry, newEntry);
251        }
252      }
253    }
254    finally
255    {
256      lock.writeLock().unlock();
257    }
258    return PostResponse.continueOperationProcessing();
259  }
260
261  /** {@inheritDoc} */
262  @Override
263  public PostResponse doPostResponse(PostResponseModifyDNOperation op)
264  {
265    final Entry oldEntry = op.getOriginalEntry();
266    final Entry newEntry = op.getUpdatedEntry();
267
268    if (op.getResultCode() != ResultCode.SUCCESS || oldEntry == null || newEntry == null) {
269      return PostResponse.continueOperationProcessing();
270    }
271
272    final DN oldDN = oldEntry.getName();
273    final DN newDN = newEntry.getName();
274
275    // Identify any client connections that may be authenticated
276    // or authorized as the user whose entry has been modified
277    // and update them with the latest version of the entry.
278    lock.writeLock().lock();
279    try
280    {
281      final Set<CopyOnWriteArraySet<ClientConnection>> arraySet = new HashSet<>();
282      userMap.removeSubtree(oldEntry.getName(), arraySet);
283      for (CopyOnWriteArraySet<ClientConnection> connectionSet : arraySet)
284      {
285        DN authNDN = null;
286        DN authZDN = null;
287        DN newAuthNDN = null;
288        DN newAuthZDN = null;
289        CopyOnWriteArraySet<ClientConnection> newAuthNSet = null;
290        CopyOnWriteArraySet<ClientConnection> newAuthZSet = null;
291        for (ClientConnection conn : connectionSet)
292        {
293          if (authNDN == null)
294          {
295            authNDN = conn.getAuthenticationInfo().getAuthenticationDN();
296            try
297            {
298              newAuthNDN = authNDN.rename(oldDN, newDN);
299            }
300            catch (Exception e)
301            {
302              // Should not happen.
303              logger.traceException(e);
304            }
305          }
306          if (authZDN == null)
307          {
308            authZDN = conn.getAuthenticationInfo().getAuthorizationDN();
309            try
310            {
311              newAuthZDN = authZDN.rename(oldDN, newDN);
312            }
313            catch (Exception e)
314            {
315              // Should not happen.
316              logger.traceException(e);
317            }
318          }
319          if (newAuthNDN != null && authNDN != null && authNDN.isDescendantOf(oldEntry.getName()))
320          {
321            if (newAuthNSet == null)
322            {
323              newAuthNSet = new CopyOnWriteArraySet<>();
324            }
325            conn.getAuthenticationInfo().setAuthenticationDN(newAuthNDN);
326            newAuthNSet.add(conn);
327          }
328          if (newAuthZDN != null && authZDN != null && authZDN.isDescendantOf(oldEntry.getName()))
329          {
330            if (newAuthZSet == null)
331            {
332              newAuthZSet = new CopyOnWriteArraySet<>();
333            }
334            conn.getAuthenticationInfo().setAuthorizationDN(newAuthZDN);
335            newAuthZSet.add(conn);
336          }
337        }
338        if (newAuthNDN != null && newAuthNSet != null)
339        {
340          userMap.put(newAuthNDN, newAuthNSet);
341        }
342        if (newAuthZDN != null && newAuthZSet != null)
343        {
344          userMap.put(newAuthZDN, newAuthZSet);
345        }
346      }
347    }
348    finally
349    {
350      lock.writeLock().unlock();
351    }
352    return PostResponse.continueOperationProcessing();
353  }
354}
355