001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020 package org.apache.directory.server.ldap; 021 022 023 import java.net.SocketAddress; 024 import java.util.Collections; 025 import java.util.HashMap; 026 import java.util.Map; 027 import java.util.concurrent.ConcurrentHashMap; 028 029 import org.apache.directory.server.core.CoreSession; 030 import org.apache.directory.server.core.LdapPrincipal; 031 import org.apache.directory.server.core.filtering.EntryFilteringCursor; 032 import org.apache.directory.server.i18n.I18n; 033 import org.apache.directory.server.ldap.handlers.controls.PagedSearchContext; 034 import org.apache.directory.shared.ldap.message.BindStatus; 035 import org.apache.directory.shared.ldap.message.internal.InternalAbandonableRequest; 036 import org.apache.mina.core.session.IoSession; 037 import org.slf4j.Logger; 038 import org.slf4j.LoggerFactory; 039 040 041 /** 042 * An object representing an LdapSession. Any connection established with the 043 * LDAP server forms a session. 044 * 045 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 046 * @version $Rev$, $Date$ 047 */ 048 public class LdapSession 049 { 050 /** The logger */ 051 private static final Logger LOG = LoggerFactory.getLogger( LdapSession.class ); 052 053 /** A speedup for logs */ 054 private static final boolean IS_DEBUG = LOG.isDebugEnabled(); 055 056 /** The list of requests we can abandon */ 057 private static final InternalAbandonableRequest[] EMPTY_ABANDONABLES = new InternalAbandonableRequest[0]; 058 059 /** A lock to protect the abandonableRequests against concurrent access */ 060 private final String outstandingLock; 061 062 /** 063 * The associated IoSession. Usually, a LdapSession is established 064 * at the user request, which means we have a IoSession. 065 */ 066 private final IoSession ioSession; 067 068 /** The CoreSession */ 069 private CoreSession coreSession; 070 071 /** A reference on the LdapServer instance */ 072 private LdapServer ldapServer; 073 074 /** A map of all the running requests */ 075 private Map<Integer, InternalAbandonableRequest> outstandingRequests; 076 077 /** The current Bind status */ 078 private BindStatus bindStatus; 079 080 /** The current mechanism used to authenticate the user */ 081 private String currentMechanism; 082 083 /** 084 * A Map containing Objects used during the SASL negotiation 085 */ 086 private Map<String, Object> saslProperties; 087 088 /** A map containing all the paged search context */ 089 private Map<Integer, PagedSearchContext> pagedSearchContexts; 090 091 092 /** 093 * Creates a new instance of LdapSession associated with the underlying 094 * connection (MINA IoSession) to the server. 095 * 096 * @param ioSession the MINA session associated this LdapSession 097 */ 098 public LdapSession( IoSession ioSession ) 099 { 100 this.ioSession = ioSession; 101 outstandingLock = "OutstandingRequestLock: " + ioSession.toString(); 102 outstandingRequests = new ConcurrentHashMap<Integer, InternalAbandonableRequest>(); 103 bindStatus = BindStatus.ANONYMOUS; 104 saslProperties = new HashMap<String, Object>(); 105 pagedSearchContexts = new HashMap<Integer, PagedSearchContext>(); 106 } 107 108 109 /** 110 * Check if the session is authenticated. There are two conditions for 111 * a session to be authenticated :<br> 112 * - the coreSession must not be null<br> 113 * - and the state should be Authenticated. 114 * 115 * @return <code>true</code> if the session is not anonymous 116 */ 117 public boolean isAuthenticated() 118 { 119 return ( coreSession != null ) && bindStatus == BindStatus.AUTHENTICATED; 120 } 121 122 123 /** 124 * Check if the session is authenticated. There are two conditions for 125 * a session to be authenticated :<br> 126 * - it has to exist<br> 127 * - and the session should not be anonymous. 128 * 129 * @return <code>true</code> if the session is not anonymous 130 */ 131 public boolean isAnonymous() 132 { 133 return bindStatus == BindStatus.ANONYMOUS; 134 } 135 136 137 /** 138 * Check if the session is processing a BindRequest, either Simple 139 * or SASL 140 * 141 * @return <code>true</code> if the session is in AuthPending state 142 */ 143 public boolean isAuthPending() 144 { 145 return ( bindStatus == BindStatus.SIMPLE_AUTH_PENDING ) || 146 ( bindStatus == BindStatus.SASL_AUTH_PENDING ); 147 } 148 149 150 /** 151 * Check if the session is processing a Simple BindRequest 152 * 153 * @return <code>true</code> if the session is in AuthPending state 154 */ 155 public boolean isSimpleAuthPending() 156 { 157 return ( bindStatus == BindStatus.SIMPLE_AUTH_PENDING ); 158 } 159 160 161 /** 162 * Check if the session is processing a SASL BindRequest 163 * 164 * @return <code>true</code> if the session is in AuthPending state 165 */ 166 public boolean isSaslAuthPending() 167 { 168 return ( bindStatus == BindStatus.SASL_AUTH_PENDING ); 169 } 170 171 172 /** 173 * Gets the MINA IoSession associated with this LdapSession. 174 * 175 * @return the MINA IoSession 176 */ 177 public IoSession getIoSession() 178 { 179 return ioSession; 180 } 181 182 183 /** 184 * Gets the logical core DirectoryService session associated with this 185 * LdapSession. 186 * 187 * @return the logical core DirectoryService session 188 */ 189 public CoreSession getCoreSession() 190 { 191 return coreSession; 192 } 193 194 195 /** 196 * Sets the logical core DirectoryService session. 197 * 198 * @param coreSession the logical core DirectoryService session 199 */ 200 public void setCoreSession( CoreSession coreSession ) 201 { 202 this.coreSession = coreSession; 203 } 204 205 206 /** 207 * Abandons all outstanding requests associated with this session. 208 */ 209 public void abandonAllOutstandingRequests() 210 { 211 synchronized ( outstandingLock ) 212 { 213 InternalAbandonableRequest[] abandonables = outstandingRequests.values().toArray( EMPTY_ABANDONABLES ); 214 215 for ( InternalAbandonableRequest abandonable : abandonables ) 216 { 217 abandonOutstandingRequest( abandonable.getMessageId() ); 218 } 219 } 220 } 221 222 223 /** 224 * Abandons a specific request by messageId. 225 * 226 * @param messageId The request ID to abandon 227 */ 228 public InternalAbandonableRequest abandonOutstandingRequest( int messageId ) 229 { 230 InternalAbandonableRequest request = null; 231 232 synchronized ( outstandingLock ) 233 { 234 request = outstandingRequests.remove( messageId ); 235 } 236 237 if ( request == null ) 238 { 239 LOG.warn( "AbandonableRequest with messageId {} not found in outstandingRequests.", messageId ); 240 return null; 241 } 242 243 if ( request.isAbandoned() ) 244 { 245 LOG.info( "AbandonableRequest with messageId {} has already been abandoned", messageId ); 246 return request; 247 } 248 249 request.abandon(); 250 251 if ( IS_DEBUG ) 252 { 253 LOG.debug( "AbandonRequest on AbandonableRequest wth messageId {} was successful.", messageId ); 254 } 255 256 return request; 257 } 258 259 260 /** 261 * Registers an outstanding request which can be abandoned later. 262 * 263 * @param request an outstanding request that can be abandoned 264 */ 265 public void registerOutstandingRequest( InternalAbandonableRequest request ) 266 { 267 synchronized( outstandingLock ) 268 { 269 outstandingRequests.put( request.getMessageId(), request ); 270 } 271 } 272 273 274 /** 275 * Unregisters an outstanding request. 276 * 277 * @param request the request to unregister 278 */ 279 public void unregisterOutstandingRequest( InternalAbandonableRequest request ) 280 { 281 synchronized( outstandingLock ) 282 { 283 outstandingRequests.remove( request.getMessageId() ); 284 } 285 } 286 287 288 /** 289 * @return A list of all the abandonable requests for this session. 290 */ 291 public Map<Integer, InternalAbandonableRequest> getOutstandingRequests() 292 { 293 synchronized( outstandingLock ) 294 { 295 return Collections.unmodifiableMap( outstandingRequests ); 296 } 297 } 298 299 300 /** 301 * @return the current bind status for this session 302 */ 303 public BindStatus getBindStatus() 304 { 305 return bindStatus; 306 } 307 308 309 /** 310 * Set the current BindStatus to Simple authentication pending 311 */ 312 public void setSimpleAuthPending() 313 { 314 bindStatus = BindStatus.SIMPLE_AUTH_PENDING; 315 } 316 317 318 /** 319 * Set the current BindStatus to SASL authentication pending 320 */ 321 public void setSaslAuthPending() 322 { 323 bindStatus = BindStatus.SASL_AUTH_PENDING; 324 } 325 326 327 /** 328 * Set the current BindStatus to Anonymous 329 */ 330 public void setAnonymous() 331 { 332 bindStatus = BindStatus.ANONYMOUS; 333 } 334 335 336 /** 337 * Set the current BindStatus to authenticated 338 */ 339 public void setAuthenticated() 340 { 341 bindStatus = BindStatus.AUTHENTICATED; 342 } 343 344 345 /** 346 * Get the mechanism selected by a user during a SASL Bind negotiation. 347 * 348 * @return The used mechanism, if any 349 */ 350 public String getCurrentMechanism() 351 { 352 return currentMechanism; 353 } 354 355 356 /** 357 * Add a Sasl property and value 358 * 359 * @param property the property to add 360 * @param value the value for this property 361 */ 362 public void putSaslProperty( String property, Object value ) 363 { 364 saslProperties.put( property, value ); 365 } 366 367 368 /** 369 * Get a Sasl property's value 370 * 371 * @param property the property to get 372 * @return the associated value, or null if we don't have such a property 373 */ 374 public Object getSaslProperty( String property ) 375 { 376 return saslProperties.get( property ); 377 } 378 379 380 /** 381 * Clear all the Sasl values stored into the Map 382 */ 383 public void clearSaslProperties() 384 { 385 saslProperties.clear(); 386 } 387 388 389 /** 390 * Remove a property from the SaslProperty map 391 * 392 * @param property the property to remove 393 */ 394 public void removeSaslProperty( String property ) 395 { 396 saslProperties.remove( property ); 397 } 398 399 400 /** 401 * @return The LdapServer reference 402 */ 403 public LdapServer getLdapServer() 404 { 405 return ldapServer; 406 } 407 408 409 /** 410 * Store a reference on the LdapServer intance 411 * 412 * @param ldapServer the LdapServer instance 413 */ 414 public void setLdapServer( LdapServer ldapServer ) 415 { 416 this.ldapServer = ldapServer; 417 } 418 419 420 /** 421 * Add a new Paged Search context into the stored context. If some 422 * context with the same id already exists, it will be closed and 423 * removed. 424 * 425 * @param context The context to add 426 */ 427 public void addPagedSearchContext( PagedSearchContext context ) throws Exception 428 { 429 synchronized ( pagedSearchContexts ) 430 { 431 PagedSearchContext oldContext = pagedSearchContexts.put( context.getCookieValue(), context ); 432 433 if ( oldContext != null ) 434 { 435 EntryFilteringCursor cursor = oldContext.getCursor(); 436 437 if ( cursor != null ) 438 { 439 try 440 { 441 cursor.close(); 442 } 443 catch ( Exception e ) 444 { 445 LOG.error( I18n.err( I18n.ERR_172, e.getLocalizedMessage() ) ); 446 } 447 } 448 } 449 } 450 } 451 452 453 /** 454 * Remove a Paged Search context from the map storing all of them. 455 * 456 * @param contextId The context ID to remove 457 * @return The removed context if any found 458 */ 459 public PagedSearchContext removePagedSearchContext( long contextId ) 460 { 461 synchronized ( pagedSearchContexts ) 462 { 463 return pagedSearchContexts.remove( contextId ); 464 } 465 } 466 467 468 /** 469 * Get paged search context associated with an ID 470 * @param contextId The id for teh context we want to get 471 * @return The associated context, if any 472 */ 473 public PagedSearchContext getPagedSearchContext( int contextId ) 474 { 475 synchronized ( pagedSearchContexts ) 476 { 477 return pagedSearchContexts.get( contextId ); 478 } 479 } 480 481 /** 482 * The principal and remote address associated with this session. 483 * @see Object#toString() 484 */ 485 public String toString() 486 { 487 if ( coreSession == null ) 488 { 489 return "LdapSession : No Ldap session ..."; 490 } 491 492 StringBuilder sb = new StringBuilder(); 493 494 LdapPrincipal principal = coreSession.getAuthenticatedPrincipal(); 495 SocketAddress address = coreSession.getClientAddress(); 496 497 sb.append( "LdapSession : <" ); 498 499 if ( principal != null ) 500 { 501 sb.append( principal.getName() ); 502 sb.append( "," ); 503 } 504 505 if ( address != null ) 506 { 507 sb.append( address ); 508 } 509 else 510 { 511 sb.append( "..." ); 512 } 513 514 sb.append( ">" ); 515 516 return sb.toString(); 517 } 518 }