View Javadoc

1   /*
2    *   Copyright 2004 The Apache Software Foundation
3    *
4    *   Licensed under the Apache License, Version 2.0 (the "License");
5    *   you may not use this file except in compliance with the License.
6    *   You may obtain a copy of the License at
7    *
8    *       http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *   Unless required by applicable law or agreed to in writing, software
11   *   distributed under the License is distributed on an "AS IS" BASIS,
12   *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *   See the License for the specific language governing permissions and
14   *   limitations under the License.
15   *
16   */
17  package org.apache.ldap.server.authn;
18  
19  
20  import java.util.ArrayList;
21  import java.util.Collection;
22  import java.util.HashMap;
23  import java.util.Iterator;
24  import java.util.Map;
25  
26  import javax.naming.Context;
27  import javax.naming.Name;
28  import javax.naming.NamingEnumeration;
29  import javax.naming.NamingException;
30  import javax.naming.directory.Attributes;
31  import javax.naming.directory.ModificationItem;
32  import javax.naming.directory.SearchControls;
33  
34  import org.apache.ldap.common.exception.LdapAuthenticationException;
35  import org.apache.ldap.common.exception.LdapAuthenticationNotSupportedException;
36  import org.apache.ldap.common.filter.ExprNode;
37  import org.apache.ldap.common.message.ResultCodeEnum;
38  import org.apache.ldap.common.util.StringTools;
39  import org.apache.ldap.server.configuration.AuthenticatorConfiguration;
40  import org.apache.ldap.server.configuration.InterceptorConfiguration;
41  import org.apache.ldap.server.interceptor.BaseInterceptor;
42  import org.apache.ldap.server.interceptor.Interceptor;
43  import org.apache.ldap.server.interceptor.NextInterceptor;
44  import org.apache.ldap.server.invocation.InvocationStack;
45  import org.apache.ldap.server.jndi.ContextFactoryConfiguration;
46  import org.apache.ldap.server.jndi.ServerContext;
47  import org.slf4j.Logger;
48  import org.slf4j.LoggerFactory;
49  
50  
51  /***
52   * An {@link Interceptor} that authenticates users.
53   *
54   * @author Apache Directory Project (dev@directory.apache.org)
55   * @author Alex Karasulu (akarasulu@apache.org)
56   * @author Trustin Lee (trustin@apache.org)
57   * @version $Rev: 264732 $, $Date: 2005-08-30 04:04:51 -0400 (Tue, 30 Aug 2005) $
58   */
59  public class AuthenticationService extends BaseInterceptor
60  {
61      private static final Logger log = LoggerFactory.getLogger( AuthenticationService.class );
62      
63      /*** authenticators **/
64      public Map authenticators = new HashMap();
65  
66      private ContextFactoryConfiguration factoryCfg;
67  
68      /***
69       * Creates an authentication service interceptor.
70       */
71      public AuthenticationService()
72      {
73      }
74  
75      /***
76       * Registers and initializes all {@link Authenticator}s to this service.
77       */
78      public void init( ContextFactoryConfiguration factoryCfg, InterceptorConfiguration cfg ) throws NamingException
79      {
80          this.factoryCfg = factoryCfg;
81  
82          // Register all authenticators
83          Iterator i = factoryCfg.getStartupConfiguration().getAuthenticatorConfigurations().iterator();
84          while( i.hasNext() )
85          {
86              try
87              {
88                  this.register( ( AuthenticatorConfiguration ) i.next() );
89              }
90              catch ( Exception e )
91              {
92                  destroy();
93                  throw ( NamingException ) new NamingException(
94                          "Failed to register authenticator." ).initCause( e );
95              }
96          }
97      }
98      
99      /***
100      * Deinitializes and deregisters all {@link Authenticator}s from this service.
101      */
102     public void destroy()
103     {
104         Iterator i = new ArrayList( authenticators.values() ).iterator();
105         while( i.hasNext() )
106         {
107             Iterator j = new ArrayList( ( Collection ) i.next() ).iterator();
108             while( j.hasNext() )
109             {
110                 unregister( ( Authenticator ) j.next() );
111             }
112         }
113         
114         authenticators.clear();
115     }
116 
117     /***
118      * Initializes the specified {@link Authenticator} and registers it to
119      * this service.
120      */
121     private void register( AuthenticatorConfiguration cfg ) throws NamingException
122     {
123         cfg.getAuthenticator().init( factoryCfg, cfg );
124 
125         Collection authenticatorList = getAuthenticators( cfg.getAuthenticator().getAuthenticatorType() );
126         if ( authenticatorList == null )
127         {
128             authenticatorList = new ArrayList();
129             authenticators.put( cfg.getAuthenticator().getAuthenticatorType(), authenticatorList );
130         }
131 
132         authenticatorList.add( cfg.getAuthenticator() );
133     }
134 
135     /***
136      * Deinitializes the specified {@link Authenticator} and deregisters it from
137      * this service.
138      */
139     private void unregister( Authenticator authenticator )
140     {
141         Collection authenticatorList = getAuthenticators( authenticator.getAuthenticatorType() );
142 
143         if ( authenticatorList == null )
144         {
145             return;
146         }
147 
148         authenticatorList.remove( authenticator );
149         
150         try
151         {
152             authenticator.destroy();
153         }
154         catch( Throwable t )
155         {
156             t.printStackTrace();
157         }
158     }
159 
160     /***
161      * Returns the list of {@link Authenticator}s with the specified type.
162      * 
163      * @return <tt>null</tt> if no authenticator is found.
164      */
165     private Collection getAuthenticators( String type )
166     {
167         Collection result = ( Collection ) authenticators.get( type );
168         if( result != null && result.size() > 0 )
169         {
170             return result;
171         }
172         else
173         {
174             return null;
175         }
176     }
177     
178 
179     public void add( NextInterceptor next, String upName, Name normName, Attributes entry ) throws NamingException
180     {
181         authenticate();
182         next.add( upName, normName, entry );
183     }
184 
185 
186     public void delete( NextInterceptor next, Name name ) throws NamingException
187     {
188         authenticate();
189         next.delete( name );
190     }
191 
192 
193     public Name getMatchedName( NextInterceptor next, Name dn, boolean normalized ) throws NamingException
194     {
195         authenticate();
196         return next.getMatchedName( dn, normalized );
197     }
198 
199 
200     public Attributes getRootDSE( NextInterceptor next ) throws NamingException
201     {
202         authenticate();
203         return next.getRootDSE();
204     }
205 
206 
207     public Name getSuffix( NextInterceptor next, Name dn, boolean normalized ) throws NamingException
208     {
209         authenticate();
210         return next.getSuffix( dn, normalized );
211     }
212 
213 
214     public boolean hasEntry( NextInterceptor next, Name name ) throws NamingException
215     {
216         authenticate();
217         return next.hasEntry( name );
218     }
219 
220 
221     public boolean isSuffix( NextInterceptor next, Name name ) throws NamingException
222     {
223         authenticate();
224         return next.isSuffix( name );
225     }
226 
227 
228     public NamingEnumeration list( NextInterceptor next, Name base ) throws NamingException
229     {
230         authenticate();
231         return next.list( base );
232     }
233 
234 
235     public Iterator listSuffixes( NextInterceptor next, boolean normalized ) throws NamingException
236     {
237         authenticate();
238         return next.listSuffixes( normalized );
239     }
240 
241 
242     public Attributes lookup( NextInterceptor next, Name dn, String[] attrIds ) throws NamingException
243     {
244         authenticate();
245         return next.lookup( dn, attrIds );
246     }
247 
248 
249     public Attributes lookup( NextInterceptor next, Name name ) throws NamingException
250     {
251         authenticate();
252         return next.lookup( name );
253     }
254 
255 
256     public void modify( NextInterceptor next, Name name, int modOp, Attributes mods ) throws NamingException
257     {
258         authenticate();
259         next.modify( name, modOp, mods );
260     }
261 
262 
263     public void modify( NextInterceptor next, Name name, ModificationItem[] mods ) throws NamingException
264     {
265         authenticate();
266         next.modify( name, mods );
267     }
268 
269 
270     public void modifyRn( NextInterceptor next, Name name, String newRn, boolean deleteOldRn ) throws NamingException
271     {
272         authenticate();
273         next.modifyRn( name, newRn, deleteOldRn );
274     }
275 
276 
277     public void move( NextInterceptor next, Name oriChildName, Name newParentName, String newRn, boolean deleteOldRn ) throws NamingException
278     {
279         authenticate();
280         next.move( oriChildName, newParentName, newRn, deleteOldRn );
281     }
282 
283 
284     public void move( NextInterceptor next, Name oriChildName, Name newParentName ) throws NamingException
285     {
286         authenticate();
287         next.move( oriChildName, newParentName );
288     }
289 
290 
291     public NamingEnumeration search( NextInterceptor next, Name base, Map env, ExprNode filter, SearchControls searchCtls ) throws NamingException
292     {
293         authenticate();
294         return next.search( base, env, filter, searchCtls );
295     }
296 
297 
298     private void authenticate() throws NamingException
299     {
300         // check if we are already authenticated and if so we return making
301         // sure first that the credentials are not exposed within context
302         ServerContext ctx =
303             ( ServerContext ) InvocationStack.getInstance().peek().getCaller();
304 
305         if ( ctx.getPrincipal() != null )
306         {
307             if ( ctx.getEnvironment().containsKey( Context.SECURITY_CREDENTIALS ) )
308             {
309                 ctx.removeFromEnvironment( Context.SECURITY_CREDENTIALS );
310             }
311             return;
312         }
313 
314         String authList = ( String ) ctx.getEnvironment().get( Context.SECURITY_AUTHENTICATION );
315 
316         if ( authList == null )
317         {
318             if ( ctx.getEnvironment().containsKey( Context.SECURITY_CREDENTIALS ) )
319             {
320                 // authentication type is simple here
321 
322                 authList = "simple";
323             }
324             else
325             {
326                 // authentication type is anonymous
327 
328                 authList = "none";
329             }
330 
331         }
332 
333         authList = StringTools.deepTrim( authList );
334 
335         String[] auth = authList.split( " " );
336 
337         Collection authenticators = null;
338 
339         // pick the first matching authenticator type
340 
341         for ( int i=0; i<auth.length; i++)
342         {
343             authenticators = getAuthenticators( auth[i] );
344 
345             if ( authenticators != null )
346             {
347                 break;
348             }
349         }
350 
351         if ( authenticators == null )
352         {
353             ctx.getEnvironment(); // shut's up idea's yellow light
354 
355             ResultCodeEnum rc = ResultCodeEnum.AUTHMETHODNOTSUPPORTED;
356 
357             throw new LdapAuthenticationNotSupportedException( rc );
358         }
359 
360         // try each authenticators
361         for ( Iterator i = authenticators.iterator(); i.hasNext(); )
362         {
363             Authenticator authenticator = ( Authenticator ) i.next();
364 
365             try
366             {
367                 // perform the authentication
368                 LdapPrincipal authorizationId = authenticator.authenticate( ctx );
369 
370                 // authentication was successful
371                 ctx.setPrincipal( new TrustedPrincipalWrapper( authorizationId ) );
372 
373                 // remove creds so there is no security risk
374                 ctx.removeFromEnvironment( Context.SECURITY_CREDENTIALS );
375                 return;
376             }
377             catch( LdapAuthenticationException e )
378             {
379                 // authentication failed, try the next authenticator
380             }
381             catch( Exception e )
382             {
383                 // Log other exceptions than LdapAuthenticationException
384                 log.warn( "Unexpected exception from " + authenticator.getClass(), e );
385             }
386         }
387 
388         throw new LdapAuthenticationException();
389     }
390 
391 
392     /***
393      * FIXME This doesn't secure anything actually.
394      * 
395      * Created this wrapper to pass to ctx.setPrincipal() which is public for added
396      * security.  This adds more security because an instance of this class is not
397      * easily accessible whereas LdapPrincipals can be accessed easily from a context
398      * althought they cannot be instantiated outside of the authn package.  Malicious
399      * code may not be able to set the principal to what they would like but they
400      * could switch existing principals using the now public ServerContext.setPrincipal()
401      * method.  To avoid this we make sure that this metho takes a TrustedPrincipalWrapper
402      * as opposed to the LdapPrincipal.  Only this service can create and call setPrincipal
403      * with a TrustedPrincipalWrapper.
404      */
405     public final class TrustedPrincipalWrapper
406     {
407         /*** the wrapped ldap principal */
408         private final LdapPrincipal principal;
409 
410 
411         /***
412          * Creates a TrustedPrincipalWrapper around an LdapPrincipal.
413          *
414          * @param principal the LdapPrincipal to wrap
415          */
416         private TrustedPrincipalWrapper( LdapPrincipal principal )
417         {
418             this.principal = principal;
419         }
420 
421 
422         /***
423          * Gets the LdapPrincipal this TrustedPrincipalWrapper wraps.
424          *
425          * @return the wrapped LdapPrincipal
426          */
427         public LdapPrincipal getPrincipal()
428         {
429             return principal;
430         }
431     }
432 }