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