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