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 }