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