blob: e8d7d13b3c85628354f038cf6e2ec73c202f386b [file] [log] [blame]
/*
* Copyright 2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.ldap.server.authn;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import org.apache.ldap.common.exception.LdapAuthenticationException;
import org.apache.ldap.common.exception.LdapAuthenticationNotSupportedException;
import org.apache.ldap.common.filter.ExprNode;
import org.apache.ldap.common.message.ResultCodeEnum;
import org.apache.ldap.common.util.StringTools;
import org.apache.ldap.server.configuration.AuthenticatorConfiguration;
import org.apache.ldap.server.configuration.InterceptorConfiguration;
import org.apache.ldap.server.interceptor.Interceptor;
import org.apache.ldap.server.interceptor.NextInterceptor;
import org.apache.ldap.server.invocation.InvocationStack;
import org.apache.ldap.server.jndi.ContextFactoryConfiguration;
import org.apache.ldap.server.jndi.ServerContext;
/**
* An {@link Interceptor} that authenticates users.
*
* @author Apache Directory Project (dev@directory.apache.org)
* @author Alex Karasulu (akarasulu@apache.org)
* @author Trustin Lee (trustin@apache.org)
* @version $Rev$, $Date$
*/
public class AuthenticationService implements Interceptor
{
/** authenticators **/
public Map authenticators = new HashMap();
private ContextFactoryConfiguration factoryCfg;
/**
* Creates an authentication service interceptor.
*/
public AuthenticationService()
{
}
/**
* Registers and initializes all {@link Authenticator}s to this service.
*/
public void init( ContextFactoryConfiguration factoryCfg, InterceptorConfiguration cfg ) throws NamingException
{
this.factoryCfg = factoryCfg;
// Register all authenticators
Iterator i = factoryCfg.getStartupConfiguration().getAuthenticatorConfigurations().iterator();
while( i.hasNext() )
{
try
{
this.register( ( AuthenticatorConfiguration ) i.next() );
}
catch ( Exception e )
{
destroy();
throw ( NamingException ) new NamingException(
"Failed to register authenticator." ).initCause( e );
}
}
}
/**
* Deinitializes and deregisters all {@link Authenticator}s from this service.
*/
public void destroy()
{
Iterator i = new ArrayList( authenticators.values() ).iterator();
while( i.hasNext() )
{
Iterator j = new ArrayList( ( Collection ) i.next() ).iterator();
while( j.hasNext() )
{
unregister( ( Authenticator ) j.next() );
}
}
authenticators.clear();
}
/**
* Initializes the specified {@link Authenticator} and registers it to
* this service.
*/
private void register( AuthenticatorConfiguration cfg ) throws NamingException
{
cfg.getAuthenticator().init( factoryCfg, cfg );
Collection authenticatorList = getAuthenticators( cfg.getAuthenticator().getAuthenticatorType() );
if ( authenticatorList == null )
{
authenticatorList = new ArrayList();
authenticators.put( cfg.getAuthenticator().getAuthenticatorType(), authenticatorList );
}
authenticatorList.add( cfg.getAuthenticator() );
}
/**
* Deinitializes the specified {@link Authenticator} and deregisters it from
* this service.
*/
private void unregister( Authenticator authenticator )
{
Collection authenticatorList = getAuthenticators( authenticator.getAuthenticatorType() );
if ( authenticatorList == null )
{
return;
}
authenticatorList.remove( authenticator );
try
{
authenticator.destroy();
}
catch( Throwable t )
{
t.printStackTrace();
}
}
/**
* Returns the list of {@link Authenticator}s with the specified type.
*
* @return <tt>null</tt> if no authenticator is found.
*/
private Collection getAuthenticators( String type )
{
Collection result = ( Collection ) authenticators.get( type );
if( result != null && result.size() > 0 )
{
return result;
}
else
{
return null;
}
}
public void add( NextInterceptor next, String upName, Name normName, Attributes entry ) throws NamingException
{
authenticate();
next.add( upName, normName, entry );
}
public void delete( NextInterceptor next, Name name ) throws NamingException
{
authenticate();
next.delete( name );
}
public Name getMatchedName( NextInterceptor next, Name dn, boolean normalized ) throws NamingException
{
authenticate();
return next.getMatchedName( dn, normalized );
}
public Attributes getRootDSE( NextInterceptor next ) throws NamingException
{
authenticate();
return next.getRootDSE();
}
public Name getSuffix( NextInterceptor next, Name dn, boolean normalized ) throws NamingException
{
authenticate();
return next.getSuffix( dn, normalized );
}
public boolean hasEntry( NextInterceptor next, Name name ) throws NamingException
{
authenticate();
return next.hasEntry( name );
}
public boolean isSuffix( NextInterceptor next, Name name ) throws NamingException
{
authenticate();
return next.isSuffix( name );
}
public NamingEnumeration list( NextInterceptor next, Name base ) throws NamingException
{
authenticate();
return next.list( base );
}
public Iterator listSuffixes( NextInterceptor next, boolean normalized ) throws NamingException
{
authenticate();
return next.listSuffixes( normalized );
}
public Attributes lookup( NextInterceptor next, Name dn, String[] attrIds ) throws NamingException
{
authenticate();
return next.lookup( dn, attrIds );
}
public Attributes lookup( NextInterceptor next, Name name ) throws NamingException
{
authenticate();
return next.lookup( name );
}
public void modify( NextInterceptor next, Name name, int modOp, Attributes mods ) throws NamingException
{
authenticate();
next.modify( name, modOp, mods );
}
public void modify( NextInterceptor next, Name name, ModificationItem[] mods ) throws NamingException
{
authenticate();
next.modify( name, mods );
}
public void modifyRn( NextInterceptor next, Name name, String newRn, boolean deleteOldRn ) throws NamingException
{
authenticate();
next.modifyRn( name, newRn, deleteOldRn );
}
public void move( NextInterceptor next, Name oriChildName, Name newParentName, String newRn, boolean deleteOldRn ) throws NamingException
{
authenticate();
next.move( oriChildName, newParentName, newRn, deleteOldRn );
}
public void move( NextInterceptor next, Name oriChildName, Name newParentName ) throws NamingException
{
authenticate();
next.move( oriChildName, newParentName );
}
public NamingEnumeration search( NextInterceptor next, Name base, Map env, ExprNode filter, SearchControls searchCtls ) throws NamingException
{
authenticate();
return next.search( base, env, filter, searchCtls );
}
private void authenticate() throws NamingException
{
// check if we are already authenticated and if so we return making
// sure first that the credentials are not exposed within context
ServerContext ctx =
( ServerContext ) InvocationStack.getInstance().peek().getCaller();
if ( ctx.getPrincipal() != null )
{
if ( ctx.getEnvironment().containsKey( Context.SECURITY_CREDENTIALS ) )
{
ctx.removeFromEnvironment( Context.SECURITY_CREDENTIALS );
}
return;
}
String authList = ( String ) ctx.getEnvironment().get( Context.SECURITY_AUTHENTICATION );
if ( authList == null )
{
if ( ctx.getEnvironment().containsKey( Context.SECURITY_CREDENTIALS ) )
{
// authentication type is simple here
authList = "simple";
}
else
{
// authentication type is anonymous
authList = "none";
}
}
authList = StringTools.deepTrim( authList );
String[] auth = authList.split( " " );
Collection authenticators = null;
// pick the first matching authenticator type
for ( int i=0; i<auth.length; i++)
{
authenticators = getAuthenticators( auth[i] );
if ( authenticators != null )
{
break;
}
}
if ( authenticators == null )
{
ctx.getEnvironment(); // shut's up idea's yellow light
ResultCodeEnum rc = ResultCodeEnum.AUTHMETHODNOTSUPPORTED;
throw new LdapAuthenticationNotSupportedException( rc );
}
// try each authenticators
for ( Iterator i = authenticators.iterator(); i.hasNext(); )
{
try
{
Authenticator authenticator = ( Authenticator ) i.next();
// perform the authentication
LdapPrincipal authorizationId = authenticator.authenticate( ctx );
// authentication was successful
ctx.setPrincipal( new TrustedPrincipalWrapper( authorizationId ) );
// remove creds so there is no security risk
ctx.removeFromEnvironment( Context.SECURITY_CREDENTIALS );
return;
}
catch ( LdapAuthenticationException e )
{
// authentication failed, try the next authenticator
}
}
throw new LdapAuthenticationException();
}
/**
* FIXME This doesn't secure anything actually.
*
* Created this wrapper to pass to ctx.setPrincipal() which is public for added
* security. This adds more security because an instance of this class is not
* easily accessible whereas LdapPrincipals can be accessed easily from a context
* althought they cannot be instantiated outside of the authn package. Malicious
* code may not be able to set the principal to what they would like but they
* could switch existing principals using the now public ServerContext.setPrincipal()
* method. To avoid this we make sure that this metho takes a TrustedPrincipalWrapper
* as opposed to the LdapPrincipal. Only this service can create and call setPrincipal
* with a TrustedPrincipalWrapper.
*/
public final class TrustedPrincipalWrapper
{
/** the wrapped ldap principal */
private final LdapPrincipal principal;
/**
* Creates a TrustedPrincipalWrapper around an LdapPrincipal.
*
* @param principal the LdapPrincipal to wrap
*/
private TrustedPrincipalWrapper( LdapPrincipal principal )
{
this.principal = principal;
}
/**
* Gets the LdapPrincipal this TrustedPrincipalWrapper wraps.
*
* @return the wrapped LdapPrincipal
*/
public LdapPrincipal getPrincipal()
{
return principal;
}
}
}