View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *  
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *  
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License. 
18   *  
19   */
20  package org.apache.directory.server.core.authn;
21  
22  
23  import java.util.ArrayList;
24  import java.util.Collection;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.Iterator;
28  import java.util.Map;
29  import java.util.Set;
30  
31  import org.apache.directory.server.core.CoreSession;
32  import org.apache.directory.server.core.DefaultCoreSession;
33  import org.apache.directory.server.core.DirectoryService;
34  import org.apache.directory.server.core.entry.ClonedServerEntry;
35  import org.apache.directory.server.core.filtering.EntryFilteringCursor;
36  import org.apache.directory.server.core.interceptor.BaseInterceptor;
37  import org.apache.directory.server.core.interceptor.Interceptor;
38  import org.apache.directory.server.core.interceptor.NextInterceptor;
39  import org.apache.directory.server.core.interceptor.context.AddOperationContext;
40  import org.apache.directory.server.core.interceptor.context.BindOperationContext;
41  import org.apache.directory.server.core.interceptor.context.CompareOperationContext;
42  import org.apache.directory.server.core.interceptor.context.DeleteOperationContext;
43  import org.apache.directory.server.core.interceptor.context.EntryOperationContext;
44  import org.apache.directory.server.core.interceptor.context.GetMatchedNameOperationContext;
45  import org.apache.directory.server.core.interceptor.context.GetRootDSEOperationContext;
46  import org.apache.directory.server.core.interceptor.context.GetSuffixOperationContext;
47  import org.apache.directory.server.core.interceptor.context.ListOperationContext;
48  import org.apache.directory.server.core.interceptor.context.ListSuffixOperationContext;
49  import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
50  import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
51  import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext;
52  import org.apache.directory.server.core.interceptor.context.MoveOperationContext;
53  import org.apache.directory.server.core.interceptor.context.OperationContext;
54  import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
55  import org.apache.directory.server.core.interceptor.context.SearchOperationContext;
56  import org.apache.directory.shared.ldap.constants.AuthenticationLevel;
57  import org.apache.directory.shared.ldap.exception.LdapAuthenticationException;
58  import org.apache.directory.shared.ldap.exception.LdapNoPermissionException;
59  import org.apache.directory.shared.ldap.exception.LdapOperationNotSupportedException;
60  import org.apache.directory.shared.ldap.message.ResultCodeEnum;
61  import org.apache.directory.shared.ldap.name.LdapDN;
62  import org.apache.directory.shared.ldap.util.StringTools;
63  
64  import org.slf4j.Logger;
65  import org.slf4j.LoggerFactory;
66  
67  
68  /**
69   * An {@link Interceptor} that authenticates users.
70   *
71   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
72   * @version $Rev: 681578 $, $Date: 2008-08-01 03:20:21 +0200 (Fr, 01 Aug 2008) $
73   * @org.apache.xbean.XBean
74   */
75  public class AuthenticationInterceptor extends BaseInterceptor
76  {
77      private static final Logger LOG = LoggerFactory.getLogger( AuthenticationInterceptor.class );
78  
79      /**
80       * Speedup for logs
81       */
82      private static final boolean IS_DEBUG = LOG.isDebugEnabled();
83  
84      private Set<Authenticator> authenticators;
85      private final Map<String, Collection<Authenticator>> authenticatorsMapByType = 
86          new HashMap<String, Collection<Authenticator>>();
87  
88      private DirectoryService directoryService;
89      
90      
91      /**
92       * Creates an authentication service interceptor.
93       */
94      public AuthenticationInterceptor()
95      {
96      }
97  
98      
99      /**
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 LdapDN 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 LdapDN 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 Iterator<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( LdapDN 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( "Attempted operation {} by unauthenticated caller.", operation.getName() );
421             throw new LdapNoPermissionException( "Attempted operation by unauthenticated caller." );
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         	throw new LdapOperationNotSupportedException( "Cannot Bind for DN " + opContext.getDn().getUpName(), ResultCodeEnum.UNWILLING_TO_PERFORM );
447         }
448 
449         Collection<Authenticator> authenticators = getAuthenticators( level.getName() );
450 
451         if ( authenticators == null )
452         {
453             LOG.debug( "No authenticators found, delegating bind to the nexus." );
454 
455             // as a last resort try binding via the nexus
456             next.bind( opContext );
457 
458             LOG.debug( "Nexus succeeded on bind operation." );
459 
460             // bind succeeded if we got this far
461             // TODO - authentication level not being set
462             LdapPrincipal principal = new LdapPrincipal( opContext.getDn(), AuthenticationLevel.SIMPLE );
463             CoreSession session = new DefaultCoreSession( principal, directoryService );
464             opContext.setSession( session );
465 
466             // remove creds so there is no security risk
467             opContext.setCredentials( null );
468             return;
469         }
470 
471         // TODO : we should refactor that.
472         // try each authenticator
473         for ( Authenticator authenticator : authenticators )
474         {
475             try
476             {
477                 // perform the authentication
478                 LdapPrincipal principal = authenticator.authenticate( opContext );
479                 
480                 LdapPrincipal clonedPrincipal = (LdapPrincipal)(principal.clone());
481 
482                 // remove creds so there is no security risk
483                 opContext.setCredentials( null );
484                 clonedPrincipal.setUserPassword( StringTools.EMPTY_BYTES );
485 
486                 // authentication was successful
487                 CoreSession session = new DefaultCoreSession( clonedPrincipal, directoryService );
488                 opContext.setSession( session );
489 
490                 return;
491             }
492             catch ( LdapAuthenticationException e )
493             {
494                 // authentication failed, try the next authenticator
495                 if ( LOG.isInfoEnabled() )
496                 {
497                     LOG.info( "Authenticator {} failed to authenticate: {}", authenticator, opContext );
498                 }
499             }
500             catch ( Exception e )
501             {
502                 // Log other exceptions than LdapAuthenticationException
503                 if ( LOG.isWarnEnabled() )
504                 {
505                     LOG.info( "Unexpected failure for Authenticator {} : {}", authenticator, opContext );
506                 }
507             }
508         }
509 
510         if ( LOG.isInfoEnabled() )
511         {
512             LOG.info( "Cannot bind to the server " );
513         }
514 
515         LdapDN dn = opContext.getDn();
516         String upDn = ( dn == null ? "" : dn.getUpName() );
517         throw new LdapAuthenticationException( "Cannot authenticate user " + upDn );
518     }
519 }