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    }