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 }