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 */ 020package org.apache.directory.server.ldap; 021 022 023import java.io.IOException; 024import java.net.SocketAddress; 025import java.util.Collections; 026import java.util.HashMap; 027import java.util.Map; 028import java.util.concurrent.ConcurrentHashMap; 029import java.util.concurrent.locks.Lock; 030import java.util.concurrent.locks.ReentrantLock; 031 032import org.apache.directory.api.ldap.model.cursor.Cursor; 033import org.apache.directory.api.ldap.model.entry.Entry; 034import org.apache.directory.api.ldap.model.message.AbandonableRequest; 035import org.apache.directory.api.ldap.model.message.BindStatus; 036import org.apache.directory.api.ldap.model.message.SearchRequest; 037import org.apache.directory.server.core.api.CoreSession; 038import org.apache.directory.server.core.api.LdapPrincipal; 039import org.apache.directory.server.core.api.SearchRequestContainer; 040import org.apache.directory.server.i18n.I18n; 041import org.apache.directory.server.ldap.handlers.controls.PagedSearchContext; 042import org.apache.mina.core.session.IoSession; 043import org.slf4j.Logger; 044import org.slf4j.LoggerFactory; 045 046 047/** 048 * An object representing an LdapSession. Any connection established with the 049 * LDAP server forms a session. 050 * 051 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 052 */ 053public class LdapSession 054{ 055 /** The logger */ 056 private static final Logger LOG = LoggerFactory.getLogger( LdapSession.class ); 057 058 /** A speedup for logs */ 059 private static final boolean IS_DEBUG = LOG.isDebugEnabled(); 060 061 /** The list of requests we can abandon */ 062 private static final AbandonableRequest[] EMPTY_ABANDONABLES = new AbandonableRequest[0]; 063 064 /** A lock to protect the abandonableRequests against concurrent access */ 065 private final Lock outstandingLock; 066 067 /** 068 * The associated IoSession. Usually, a LdapSession is established 069 * at the user request, which means we have a IoSession. 070 */ 071 private final IoSession ioSession; 072 073 /** The CoreSession */ 074 private CoreSession coreSession; 075 076 /** A reference on the LdapServer instance */ 077 private LdapServer ldapServer; 078 079 /** A map of all the running requests */ 080 private Map<Integer, AbandonableRequest> outstandingRequests; 081 082 /** A map of all the pending search requests */ 083 private Map<Integer, SearchRequestContainer> searchRequests; 084 085 /** The current Bind status */ 086 private BindStatus bindStatus; 087 088 /** The current mechanism used to authenticate the user */ 089 private String currentMechanism; 090 091 /** 092 * A Map containing Objects used during the SASL negotiation 093 */ 094 private Map<String, Object> saslProperties; 095 096 /** A map containing all the paged search context */ 097 private Map<Integer, PagedSearchContext> pagedSearchContexts; 098 099 100 /** 101 * Creates a new instance of LdapSession associated with the underlying 102 * connection (MINA IoSession) to the server. 103 * 104 * @param ioSession the MINA session associated this LdapSession 105 */ 106 public LdapSession( IoSession ioSession ) 107 { 108 this.ioSession = ioSession; 109 outstandingLock = new ReentrantLock(); 110 outstandingRequests = new ConcurrentHashMap<>(); 111 searchRequests = new ConcurrentHashMap<>(); 112 bindStatus = BindStatus.ANONYMOUS; 113 saslProperties = new HashMap<>(); 114 pagedSearchContexts = new ConcurrentHashMap<>(); 115 } 116 117 118 /** 119 * Check if the session is authenticated. There are two conditions for 120 * a session to be authenticated :<br> 121 * - the coreSession must not be null<br> 122 * - and the state should be Authenticated. 123 * 124 * @return <code>true</code> if the session is not anonymous 125 */ 126 public boolean isAuthenticated() 127 { 128 return ( coreSession != null ) && ( bindStatus == BindStatus.AUTHENTICATED ); 129 } 130 131 132 /** 133 * Check if the session is authenticated. There are two conditions for 134 * a session to be authenticated :<br> 135 * - it has to exist<br> 136 * - and the session should not be anonymous. 137 * 138 * @return <code>true</code> if the session is not anonymous 139 */ 140 public boolean isAnonymous() 141 { 142 return bindStatus == BindStatus.ANONYMOUS; 143 } 144 145 146 /** 147 * Check if the session is processing a BindRequest, either Simple 148 * or SASL 149 * 150 * @return <code>true</code> if the session is in AuthPending state 151 */ 152 public boolean isAuthPending() 153 { 154 return ( bindStatus == BindStatus.SIMPLE_AUTH_PENDING ) || ( bindStatus == BindStatus.SASL_AUTH_PENDING ); 155 } 156 157 158 /** 159 * Check if the session is processing a Simple BindRequest 160 * 161 * @return <code>true</code> if the session is in AuthPending state 162 */ 163 public boolean isSimpleAuthPending() 164 { 165 return bindStatus == BindStatus.SIMPLE_AUTH_PENDING; 166 } 167 168 169 /** 170 * Check if the session is processing a SASL BindRequest 171 * 172 * @return <code>true</code> if the session is in AuthPending state 173 */ 174 public boolean isSaslAuthPending() 175 { 176 return bindStatus == BindStatus.SASL_AUTH_PENDING; 177 } 178 179 180 /** 181 * Gets the MINA IoSession associated with this LdapSession. 182 * 183 * @return the MINA IoSession 184 */ 185 public IoSession getIoSession() 186 { 187 return ioSession; 188 } 189 190 191 /** 192 * Gets the logical core DirectoryService session associated with this 193 * LdapSession. 194 * 195 * @return the logical core DirectoryService session 196 */ 197 public CoreSession getCoreSession() 198 { 199 return coreSession; 200 } 201 202 203 /** 204 * Sets the logical core DirectoryService session. 205 * 206 * @param coreSession the logical core DirectoryService session 207 */ 208 public void setCoreSession( CoreSession coreSession ) 209 { 210 this.coreSession = coreSession; 211 } 212 213 214 /** 215 * Abandons all outstanding requests associated with this session. 216 */ 217 public void abandonAllOutstandingRequests() 218 { 219 try 220 { 221 outstandingLock.lock(); 222 AbandonableRequest[] abandonables = outstandingRequests.values().toArray( EMPTY_ABANDONABLES ); 223 224 for ( AbandonableRequest abandonable : abandonables ) 225 { 226 abandonOutstandingRequest( abandonable.getMessageId() ); 227 } 228 } 229 finally 230 { 231 outstandingLock.unlock(); 232 } 233 } 234 235 236 /** 237 * Abandons a specific request by messageId. 238 * 239 * @param messageId The request ID to abandon 240 * @return The found request 241 */ 242 public AbandonableRequest abandonOutstandingRequest( int messageId ) 243 { 244 AbandonableRequest request = null; 245 246 try 247 { 248 outstandingLock.lock(); 249 request = outstandingRequests.remove( messageId ); 250 } 251 finally 252 { 253 outstandingLock.unlock(); 254 } 255 256 // Remove the PagedSearch cursors now 257 try 258 { 259 closeAllPagedSearches(); 260 } 261 catch ( Exception e ) 262 { 263 LOG.error( I18n.err( I18n.ERR_172, e.getLocalizedMessage() ) ); 264 } 265 266 if ( request == null ) 267 { 268 LOG.warn( "AbandonableRequest with messageId {} not found in outstandingRequests.", messageId ); 269 270 return null; 271 } 272 273 if ( request.isAbandoned() ) 274 { 275 LOG.info( "AbandonableRequest with messageId {} has already been abandoned", messageId ); 276 277 return request; 278 } 279 280 request.abandon(); 281 282 if ( IS_DEBUG ) 283 { 284 LOG.debug( "AbandonRequest on AbandonableRequest wth messageId {} was successful.", messageId ); 285 } 286 287 return request; 288 } 289 290 291 /** 292 * Registers an outstanding request which can be abandoned later. 293 * 294 * @param request an outstanding request that can be abandoned 295 */ 296 public void registerOutstandingRequest( AbandonableRequest request ) 297 { 298 try 299 { 300 outstandingLock.lock(); 301 outstandingRequests.put( request.getMessageId(), request ); 302 } 303 finally 304 { 305 outstandingLock.unlock(); 306 } 307 308 } 309 310 311 /** 312 * Unregisters an outstanding request. 313 * 314 * @param request the request to unregister 315 */ 316 public void unregisterOutstandingRequest( AbandonableRequest request ) 317 { 318 try 319 { 320 outstandingLock.lock(); 321 outstandingRequests.remove( request.getMessageId() ); 322 } 323 finally 324 { 325 outstandingLock.unlock(); 326 } 327 } 328 329 330 /** 331 * @return A list of all the abandonable requests for this session. 332 */ 333 public Map<Integer, AbandonableRequest> getOutstandingRequests() 334 { 335 try 336 { 337 outstandingLock.lock(); 338 339 return Collections.unmodifiableMap( outstandingRequests ); 340 } 341 finally 342 { 343 outstandingLock.unlock(); 344 } 345 } 346 347 348 /** 349 * Registers a new searchRequest 350 * 351 * @param searchRequest a new searchRequest 352 * @param cursor The cursor to register 353 */ 354 public void registerSearchRequest( SearchRequest searchRequest, Cursor<Entry> cursor ) 355 { 356 try 357 { 358 outstandingLock.lock(); 359 SearchRequestContainer searchRequestContainer = new SearchRequestContainer( searchRequest, cursor ); 360 searchRequests.put( searchRequest.getMessageId(), searchRequestContainer ); 361 } 362 finally 363 { 364 outstandingLock.unlock(); 365 } 366 } 367 368 369 /** 370 * Unregisters a completed search request. 371 * 372 * @param searchRequest the searchRequest to unregister 373 */ 374 public void unregisterSearchRequest( SearchRequest searchRequest ) 375 { 376 searchRequests.remove( searchRequest.getMessageId() ); 377 } 378 379 380 /** 381 * Find the searchRequestContainer associated with a MessageID 382 * 383 * @param messageId the SearchRequestContainer MessageID we are looking for 384 * @return The found SearchRequestContainer 385 */ 386 public SearchRequestContainer getSearchRequest( int messageId ) 387 { 388 return searchRequests.get( messageId ); 389 } 390 391 392 /** 393 * @return the current bind status for this session 394 */ 395 public BindStatus getBindStatus() 396 { 397 return bindStatus; 398 } 399 400 401 /** 402 * Set the current BindStatus to Simple authentication pending 403 */ 404 public void setSimpleAuthPending() 405 { 406 bindStatus = BindStatus.SIMPLE_AUTH_PENDING; 407 } 408 409 410 /** 411 * Set the current BindStatus to SASL authentication pending 412 */ 413 public void setSaslAuthPending() 414 { 415 bindStatus = BindStatus.SASL_AUTH_PENDING; 416 } 417 418 419 /** 420 * Set the current BindStatus to Anonymous 421 */ 422 public void setAnonymous() 423 { 424 bindStatus = BindStatus.ANONYMOUS; 425 } 426 427 428 /** 429 * Set the current BindStatus to authenticated 430 */ 431 public void setAuthenticated() 432 { 433 bindStatus = BindStatus.AUTHENTICATED; 434 } 435 436 437 /** 438 * Get the mechanism selected by a user during a SASL Bind negotiation. 439 * 440 * @return The used mechanism, if any 441 */ 442 public String getCurrentMechanism() 443 { 444 return currentMechanism; 445 } 446 447 448 /** 449 * Add a Sasl property and value 450 * 451 * @param property the property to add 452 * @param value the value for this property 453 */ 454 public void putSaslProperty( String property, Object value ) 455 { 456 saslProperties.put( property, value ); 457 } 458 459 460 /** 461 * Get a Sasl property's value 462 * 463 * @param property the property to get 464 * @return the associated value, or null if we don't have such a property 465 */ 466 public Object getSaslProperty( String property ) 467 { 468 return saslProperties.get( property ); 469 } 470 471 472 /** 473 * Clear all the Sasl values stored into the Map 474 */ 475 public void clearSaslProperties() 476 { 477 saslProperties.clear(); 478 } 479 480 481 /** 482 * Remove a property from the SaslProperty map 483 * 484 * @param property the property to remove 485 */ 486 public void removeSaslProperty( String property ) 487 { 488 saslProperties.remove( property ); 489 } 490 491 492 /** 493 * @return The LdapServer reference 494 */ 495 public LdapServer getLdapServer() 496 { 497 return ldapServer; 498 } 499 500 501 /** 502 * Store a reference on the LdapServer intance 503 * 504 * @param ldapServer the LdapServer instance 505 */ 506 public void setLdapServer( LdapServer ldapServer ) 507 { 508 this.ldapServer = ldapServer; 509 } 510 511 512 /** 513 * Add a new Paged Search context into the stored context. If some 514 * context with the same id already exists, it will be closed and 515 * removed. 516 * 517 * @param context The context to add 518 */ 519 public void addPagedSearchContext( PagedSearchContext context ) 520 { 521 PagedSearchContext oldContext = pagedSearchContexts.put( context.getCookieValue(), context ); 522 523 if ( oldContext != null ) 524 { 525 // ??? Very unlikely to happen ... 526 Cursor<Entry> cursor = oldContext.getCursor(); 527 528 if ( cursor != null ) 529 { 530 try 531 { 532 cursor.close(); 533 } 534 catch ( Exception e ) 535 { 536 LOG.error( I18n.err( I18n.ERR_172, e.getLocalizedMessage() ) ); 537 } 538 } 539 } 540 } 541 542 543 /** 544 * Remove a Paged Search context from the map storing all of them. 545 * 546 * @param contextId The context ID to remove 547 * @return The removed context if any found 548 */ 549 public PagedSearchContext removePagedSearchContext( int contextId ) 550 { 551 return pagedSearchContexts.remove( contextId ); 552 } 553 554 555 /** 556 * Close all the pending cursors for all the pending PagedSearches 557 * 558 * @throws IOException If we've got an exception. 559 */ 560 public void closeAllPagedSearches() throws IOException 561 { 562 for ( Map.Entry<Integer, PagedSearchContext> entry : pagedSearchContexts.entrySet() ) 563 { 564 Cursor<Entry> cursor = entry.getValue().getCursor(); 565 566 if ( cursor != null ) 567 { 568 cursor.close(); 569 } 570 } 571 } 572 573 /** 574 * Get paged search context associated with an ID 575 * @param contextId The id for teh context we want to get 576 * @return The associated context, if any 577 */ 578 public PagedSearchContext getPagedSearchContext( int contextId ) 579 { 580 return pagedSearchContexts.get( contextId ); 581 } 582 583 584 /** 585 * The principal and remote address associated with this session. 586 * @see Object#toString() 587 */ 588 public String toString() 589 { 590 if ( coreSession == null ) 591 { 592 return "LdapSession : No Ldap session ..."; 593 } 594 595 StringBuilder sb = new StringBuilder(); 596 597 LdapPrincipal principal = coreSession.getAuthenticatedPrincipal(); 598 SocketAddress address = coreSession.getClientAddress(); 599 600 sb.append( "LdapSession : <" ); 601 602 if ( principal != null ) 603 { 604 sb.append( principal ); 605 sb.append( "," ); 606 } 607 608 if ( address != null ) 609 { 610 sb.append( address ); 611 } 612 else 613 { 614 sb.append( "..." ); 615 } 616 617 sb.append( ">" ); 618 619 return sb.toString(); 620 } 621}