View Javadoc
1   /*
2    *   Licensed to the Apache Software Foundation (ASF) under one
3    *   or more contributor license agreements.  See the NOTICE file
4    *   distributed with this work for additional information
5    *   regarding copyright ownership.  The ASF licenses this file
6    *   to you under the Apache License, Version 2.0 (the
7    *   "License"); you may not use this file except in compliance
8    *   with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *   Unless required by applicable law or agreed to in writing,
13   *   software distributed under the License is distributed on an
14   *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *   KIND, either express or implied.  See the License for the
16   *   specific language governing permissions and limitations
17   *   under the License.
18   *
19   */
20  package org.apache.directory.server.ldap;
21  
22  
23  import java.io.IOException;
24  import java.net.SocketAddress;
25  import java.util.Collections;
26  import java.util.HashMap;
27  import java.util.Map;
28  import java.util.concurrent.ConcurrentHashMap;
29  import java.util.concurrent.locks.Lock;
30  import java.util.concurrent.locks.ReentrantLock;
31  
32  import org.apache.directory.api.ldap.model.cursor.Cursor;
33  import org.apache.directory.api.ldap.model.entry.Entry;
34  import org.apache.directory.api.ldap.model.message.AbandonableRequest;
35  import org.apache.directory.api.ldap.model.message.BindStatus;
36  import org.apache.directory.api.ldap.model.message.SearchRequest;
37  import org.apache.directory.server.core.api.CoreSession;
38  import org.apache.directory.server.core.api.LdapPrincipal;
39  import org.apache.directory.server.core.api.SearchRequestContainer;
40  import org.apache.directory.server.i18n.I18n;
41  import org.apache.directory.server.ldap.handlers.controls.PagedSearchContext;
42  import org.apache.mina.core.session.IoSession;
43  import org.slf4j.Logger;
44  import org.slf4j.LoggerFactory;
45  
46  
47  /**
48   * An object representing an LdapSession. Any connection established with the
49   * LDAP server forms a session.
50   *
51   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
52   */
53  public class LdapSession
54  {
55      /** The logger */
56      private static final Logger LOG = LoggerFactory.getLogger( LdapSession.class );
57  
58      /** A speedup for logs */
59      private static final boolean IS_DEBUG = LOG.isDebugEnabled();
60  
61      /** The list of requests we can abandon */
62      private static final AbandonableRequest[] EMPTY_ABANDONABLES = new AbandonableRequest[0];
63  
64      /** A lock to protect the abandonableRequests against concurrent access */
65      private final Lock outstandingLock;
66  
67      /**
68       * The associated IoSession. Usually, a LdapSession is established
69       * at the user request, which means we have a IoSession.
70       */
71      private final IoSession ioSession;
72  
73      /** The CoreSession */
74      private CoreSession coreSession;
75  
76      /** A reference on the LdapServer instance */
77      private LdapServer ldapServer;
78  
79      /** A map of all the running requests */
80      private Map<Integer, AbandonableRequest> outstandingRequests;
81  
82      /** A map of all the pending search requests */
83      private Map<Integer, SearchRequestContainer> searchRequests;
84  
85      /** The current Bind status */
86      private BindStatus bindStatus;
87  
88      /** The current mechanism used to authenticate the user */
89      private String currentMechanism;
90  
91      /**
92       * A Map containing Objects used during the SASL negotiation
93       */
94      private Map<String, Object> saslProperties;
95  
96      /** A map containing all the paged search context */
97      private Map<Integer, PagedSearchContext> pagedSearchContexts;
98  
99  
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             SearchRequestContainerer.html#SearchRequestContainer">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 }