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.core.authn;
021
022
023 import java.util.ArrayList;
024 import java.util.Collection;
025 import java.util.HashMap;
026 import java.util.HashSet;
027 import java.util.Map;
028 import java.util.Set;
029
030 import org.apache.directory.server.core.CoreSession;
031 import org.apache.directory.server.core.DefaultCoreSession;
032 import org.apache.directory.server.core.DirectoryService;
033 import org.apache.directory.server.core.LdapPrincipal;
034 import org.apache.directory.server.core.entry.ClonedServerEntry;
035 import org.apache.directory.server.core.filtering.EntryFilteringCursor;
036 import org.apache.directory.server.core.interceptor.BaseInterceptor;
037 import org.apache.directory.server.core.interceptor.Interceptor;
038 import org.apache.directory.server.core.interceptor.NextInterceptor;
039 import org.apache.directory.server.core.interceptor.context.AddOperationContext;
040 import org.apache.directory.server.core.interceptor.context.BindOperationContext;
041 import org.apache.directory.server.core.interceptor.context.CompareOperationContext;
042 import org.apache.directory.server.core.interceptor.context.DeleteOperationContext;
043 import org.apache.directory.server.core.interceptor.context.EntryOperationContext;
044 import org.apache.directory.server.core.interceptor.context.GetMatchedNameOperationContext;
045 import org.apache.directory.server.core.interceptor.context.GetRootDSEOperationContext;
046 import org.apache.directory.server.core.interceptor.context.GetSuffixOperationContext;
047 import org.apache.directory.server.core.interceptor.context.ListOperationContext;
048 import org.apache.directory.server.core.interceptor.context.ListSuffixOperationContext;
049 import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
050 import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
051 import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext;
052 import org.apache.directory.server.core.interceptor.context.MoveOperationContext;
053 import org.apache.directory.server.core.interceptor.context.OperationContext;
054 import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
055 import org.apache.directory.server.core.interceptor.context.SearchOperationContext;
056 import org.apache.directory.server.i18n.I18n;
057 import org.apache.directory.shared.ldap.constants.AuthenticationLevel;
058 import org.apache.directory.shared.ldap.exception.LdapAuthenticationException;
059 import org.apache.directory.shared.ldap.exception.LdapNoPermissionException;
060 import org.apache.directory.shared.ldap.exception.LdapUnwillingToPerformException;
061 import org.apache.directory.shared.ldap.message.ResultCodeEnum;
062 import org.apache.directory.shared.ldap.name.DN;
063 import org.apache.directory.shared.ldap.util.StringTools;
064 import org.slf4j.Logger;
065 import org.slf4j.LoggerFactory;
066
067
068 /**
069 * An {@link Interceptor} that authenticates users.
070 *
071 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
072 * @version $Rev: 923747 $, $Date: 2010-03-16 16:09:38 +0200 (Tue, 16 Mar 2010) $
073 * @org.apache.xbean.XBean
074 */
075 public class AuthenticationInterceptor extends BaseInterceptor
076 {
077 private static final Logger LOG = LoggerFactory.getLogger( AuthenticationInterceptor.class );
078
079 /**
080 * Speedup for logs
081 */
082 private static final boolean IS_DEBUG = LOG.isDebugEnabled();
083
084 private Set<Authenticator> authenticators;
085 private final Map<String, Collection<Authenticator>> authenticatorsMapByType =
086 new HashMap<String, Collection<Authenticator>>();
087
088 private DirectoryService directoryService;
089
090
091 /**
092 * Creates an authentication service interceptor.
093 */
094 public AuthenticationInterceptor()
095 {
096 }
097
098
099 /**
100 * Registers and initializes all {@link Authenticator}s to this service.
101 */
102 public void init( DirectoryService directoryService ) throws Exception
103 {
104 this.directoryService = directoryService;
105
106 if ( authenticators == null )
107 {
108 setDefaultAuthenticators();
109 }
110 // Register all authenticators
111 for ( Authenticator authenticator : authenticators )
112 {
113 register( authenticator, directoryService );
114 }
115 }
116
117
118 private void setDefaultAuthenticators()
119 {
120 Set<Authenticator> set = new HashSet<Authenticator>();
121 set.add( new AnonymousAuthenticator() );
122 set.add( new SimpleAuthenticator() );
123 set.add( new StrongAuthenticator() );
124
125 setAuthenticators( set );
126 }
127
128
129 public Set<Authenticator> getAuthenticators()
130 {
131 return authenticators;
132 }
133
134
135 /**
136 * @param authenticators authenticators to be used by this AuthenticationInterceptor
137 * @org.apache.xbean.Property nestedType="org.apache.directory.server.core.authn.Authenticator"
138 */
139 public void setAuthenticators( Set<Authenticator> authenticators )
140 {
141 this.authenticators = authenticators;
142 }
143
144
145 /**
146 * Deinitializes and deregisters all {@link Authenticator}s from this service.
147 */
148 public void destroy()
149 {
150 authenticatorsMapByType.clear();
151 Set<Authenticator> copy = new HashSet<Authenticator>( authenticators );
152 authenticators = null;
153 for ( Authenticator authenticator : copy )
154 {
155 authenticator.destroy();
156 }
157 }
158
159
160 /**
161 * Initializes the specified {@link Authenticator} and registers it to
162 * this service.
163 *
164 * @param authenticator Authenticator to initialize and register by type
165 * @param directoryService configuration info to supply to the Authenticator during initialization
166 * @throws javax.naming.Exception if initialization fails.
167 */
168 private void register( Authenticator authenticator, DirectoryService directoryService ) throws Exception
169 {
170 authenticator.init( directoryService );
171
172 Collection<Authenticator> authenticatorList = getAuthenticators( authenticator.getAuthenticatorType() );
173
174 if ( authenticatorList == null )
175 {
176 authenticatorList = new ArrayList<Authenticator>();
177 authenticatorsMapByType.put( authenticator.getAuthenticatorType(), authenticatorList );
178 }
179
180 authenticatorList.add( authenticator );
181 }
182
183
184 /**
185 * Returns the list of {@link Authenticator}s with the specified type.
186 *
187 * @param type type of Authenticator sought
188 * @return A list of Authenticators of the requested type or <tt>null</tt> if no authenticator is found.
189 */
190 private Collection<Authenticator> getAuthenticators( String type )
191 {
192 Collection<Authenticator> result = authenticatorsMapByType.get( type );
193
194 if ( ( result != null ) && ( result.size() > 0 ) )
195 {
196 return result;
197 }
198 else
199 {
200 return null;
201 }
202 }
203
204
205 public void add( NextInterceptor next, AddOperationContext opContext ) throws Exception
206 {
207 if ( IS_DEBUG )
208 {
209 LOG.debug( "Operation Context: {}", opContext );
210 }
211
212 checkAuthenticated( opContext );
213 next.add( opContext );
214 }
215
216
217 public void delete( NextInterceptor next, DeleteOperationContext opContext ) throws Exception
218 {
219 if ( IS_DEBUG )
220 {
221 LOG.debug( "Operation Context: {}", opContext );
222 }
223
224 checkAuthenticated( opContext );
225 next.delete( opContext );
226 invalidateAuthenticatorCaches( opContext.getDn() );
227 }
228
229
230 public DN getMatchedName( NextInterceptor next, GetMatchedNameOperationContext opContext ) throws Exception
231 {
232 if ( IS_DEBUG )
233 {
234 LOG.debug( "Operation Context: {}", opContext );
235 }
236
237 checkAuthenticated( opContext );
238 return next.getMatchedName( opContext );
239 }
240
241
242 public ClonedServerEntry getRootDSE( NextInterceptor next, GetRootDSEOperationContext opContext ) throws Exception
243 {
244 if ( IS_DEBUG )
245 {
246 LOG.debug( "Operation Context: {}", opContext );
247 }
248
249 checkAuthenticated( opContext );
250 return next.getRootDSE( opContext );
251 }
252
253
254 public DN getSuffix( NextInterceptor next, GetSuffixOperationContext opContext ) throws Exception
255 {
256 if ( IS_DEBUG )
257 {
258 LOG.debug( "Operation Context: {}", opContext );
259 }
260
261 checkAuthenticated( opContext );
262 return next.getSuffix( opContext );
263 }
264
265
266 public boolean hasEntry( NextInterceptor next, EntryOperationContext opContext ) throws Exception
267 {
268 if ( IS_DEBUG )
269 {
270 LOG.debug( "Operation Context: {}", opContext );
271 }
272
273 checkAuthenticated( opContext );
274 return next.hasEntry( opContext );
275 }
276
277
278 public EntryFilteringCursor list( NextInterceptor next, ListOperationContext opContext ) throws Exception
279 {
280 if ( IS_DEBUG )
281 {
282 LOG.debug( "Operation Context: {}", opContext );
283 }
284
285 checkAuthenticated( opContext );
286 return next.list( opContext );
287 }
288
289
290 public Set<String> listSuffixes( NextInterceptor next, ListSuffixOperationContext opContext ) throws Exception
291 {
292 if ( IS_DEBUG )
293 {
294 LOG.debug( "Operation Context: {}", opContext );
295 }
296
297 checkAuthenticated( opContext );
298 return next.listSuffixes( opContext );
299 }
300
301
302 public ClonedServerEntry lookup( NextInterceptor next, LookupOperationContext opContext ) throws Exception
303 {
304 if ( IS_DEBUG )
305 {
306 LOG.debug( "Operation Context: {}", opContext );
307 }
308
309 checkAuthenticated( opContext );
310 return next.lookup( opContext );
311 }
312
313
314 private void invalidateAuthenticatorCaches( DN principalDn )
315 {
316 for ( String authMech : authenticatorsMapByType.keySet() )
317 {
318 Collection<Authenticator> authenticators = getAuthenticators( authMech );
319
320 // try each authenticator
321 for ( Authenticator authenticator : authenticators )
322 {
323 authenticator.invalidateCache( principalDn );
324 }
325 }
326 }
327
328
329 public void modify( NextInterceptor next, ModifyOperationContext opContext ) throws Exception
330 {
331 if ( IS_DEBUG )
332 {
333 LOG.debug( "Operation Context: {}", opContext );
334 }
335
336 checkAuthenticated( opContext );
337 next.modify( opContext );
338 invalidateAuthenticatorCaches( opContext.getDn() );
339 }
340
341
342 public void rename( NextInterceptor next, RenameOperationContext opContext ) throws Exception
343 {
344 if ( IS_DEBUG )
345 {
346 LOG.debug( "Operation Context: {}", opContext );
347 }
348
349 checkAuthenticated( opContext );
350 next.rename( opContext );
351 invalidateAuthenticatorCaches( opContext.getDn() );
352 }
353
354
355 public boolean compare( NextInterceptor next, CompareOperationContext opContext ) throws Exception
356 {
357 if ( IS_DEBUG )
358 {
359 LOG.debug( "Operation Context: {}", opContext );
360 }
361
362 checkAuthenticated( opContext );
363 boolean result = next.compare( opContext );
364 invalidateAuthenticatorCaches( opContext.getDn() );
365 return result;
366 }
367
368
369 public void moveAndRename( NextInterceptor next, MoveAndRenameOperationContext opContext )
370 throws Exception
371 {
372 if ( IS_DEBUG )
373 {
374 LOG.debug( "Operation Context: {}", opContext );
375 }
376
377 checkAuthenticated( opContext );
378 next.moveAndRename( opContext );
379 invalidateAuthenticatorCaches( opContext.getDn() );
380 }
381
382
383 public void move( NextInterceptor next, MoveOperationContext opContext ) throws Exception
384 {
385 if ( IS_DEBUG )
386 {
387 LOG.debug( "Operation Context: {}", opContext );
388 }
389
390 checkAuthenticated( opContext );
391 next.move( opContext );
392 invalidateAuthenticatorCaches( opContext.getDn() );
393 }
394
395
396 public EntryFilteringCursor search( NextInterceptor next, SearchOperationContext opContext ) throws Exception
397 {
398 if ( IS_DEBUG )
399 {
400 LOG.debug( "Operation Context: {}", opContext );
401 }
402
403 checkAuthenticated( opContext );
404 return next.search( opContext );
405 }
406
407
408 /**
409 * Check if the current operation has a valid PrincipalDN or not.
410 *
411 * @param opContext the OperationContext for this operation
412 * @param operation the operation type
413 * @throws Exception
414 */
415 private void checkAuthenticated( OperationContext operation ) throws Exception
416 {
417 if ( operation.getSession().isAnonymous() && !directoryService.isAllowAnonymousAccess()
418 && !operation.getDn().isEmpty() )
419 {
420 LOG.error( I18n.err( I18n.ERR_5, operation.getName() ) );
421 throw new LdapNoPermissionException( I18n.err( I18n.ERR_5, operation.getName() ) );
422 }
423 }
424
425
426 public void bind( NextInterceptor next, BindOperationContext opContext ) throws Exception
427 {
428 if ( IS_DEBUG )
429 {
430 LOG.debug( "Operation Context: {}", opContext );
431 }
432
433 if ( ( opContext.getSession() != null ) && ( opContext.getSession().getEffectivePrincipal() != null ) )
434 {
435 // null out the credentials
436 opContext.setCredentials( null );
437 }
438
439 // pick the first matching authenticator type
440 AuthenticationLevel level = opContext.getAuthenticationLevel();
441
442 if ( level == AuthenticationLevel.UNAUTHENT )
443 {
444 // This is a case where the Bind request contains a DN, but no password.
445 // We don't check the DN, we just return a UnwillingToPerform error
446 // Cf RFC 4513, chap. 5.1.2
447 throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, "Cannot Bind for DN " + opContext.getDn().getName() );
448 }
449
450 Collection<Authenticator> authenticators = getAuthenticators( level.getName() );
451
452 if ( authenticators == null )
453 {
454 LOG.debug( "No authenticators found, delegating bind to the nexus." );
455
456 // as a last resort try binding via the nexus
457 next.bind( opContext );
458
459 LOG.debug( "Nexus succeeded on bind operation." );
460
461 // bind succeeded if we got this far
462 // TODO - authentication level not being set
463 LdapPrincipal principal = new LdapPrincipal( opContext.getDn(), AuthenticationLevel.SIMPLE );
464 CoreSession session = new DefaultCoreSession( principal, directoryService );
465 opContext.setSession( session );
466
467 // remove creds so there is no security risk
468 opContext.setCredentials( null );
469 return;
470 }
471
472 // TODO : we should refactor that.
473 // try each authenticator
474 for ( Authenticator authenticator : authenticators )
475 {
476 try
477 {
478 // perform the authentication
479 LdapPrincipal principal = authenticator.authenticate( opContext );
480
481 LdapPrincipal clonedPrincipal = (LdapPrincipal)(principal.clone());
482
483 // remove creds so there is no security risk
484 opContext.setCredentials( null );
485 clonedPrincipal.setUserPassword( StringTools.EMPTY_BYTES );
486
487 // authentication was successful
488 CoreSession session = new DefaultCoreSession( clonedPrincipal, directoryService );
489 opContext.setSession( session );
490
491 return;
492 }
493 catch ( LdapAuthenticationException e )
494 {
495 // authentication failed, try the next authenticator
496 if ( LOG.isInfoEnabled() )
497 {
498 LOG.info( "Authenticator {} failed to authenticate: {}", authenticator, opContext );
499 }
500 }
501 catch ( Exception e )
502 {
503 // Log other exceptions than LdapAuthenticationException
504 if ( LOG.isWarnEnabled() )
505 {
506 LOG.info( "Unexpected failure for Authenticator {} : {}", authenticator, opContext );
507 }
508 }
509 }
510
511 if ( LOG.isInfoEnabled() )
512 {
513 LOG.info( "Cannot bind to the server " );
514 }
515
516 DN dn = opContext.getDn();
517 String upDn = ( dn == null ? "" : dn.getName() );
518 throw new LdapAuthenticationException( I18n.err( I18n.ERR_229, upDn ) );
519 }
520 }