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