001package org.apache.archiva.redback.authentication.ldap;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 * http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import org.apache.archiva.redback.authentication.AbstractAuthenticator;
023import org.apache.archiva.redback.common.ldap.LdapUtils;
024import org.apache.archiva.redback.common.ldap.connection.DefaultLdapConnection;
025import org.apache.archiva.redback.common.ldap.connection.LdapConnection;
026import org.apache.archiva.redback.common.ldap.user.UserMapper;
027import org.apache.archiva.redback.common.ldap.connection.LdapConnectionFactory;
028import org.apache.archiva.redback.configuration.UserConfiguration;
029import org.apache.archiva.redback.configuration.UserConfigurationKeys;
030import org.apache.commons.lang3.StringUtils;
031import org.apache.archiva.redback.authentication.AuthenticationDataSource;
032import org.apache.archiva.redback.authentication.AuthenticationException;
033import org.apache.archiva.redback.authentication.AuthenticationResult;
034import org.apache.archiva.redback.authentication.Authenticator;
035import org.apache.archiva.redback.authentication.PasswordBasedAuthenticationDataSource;
036import org.apache.archiva.redback.common.ldap.connection.LdapException;
037import org.apache.archiva.redback.users.ldap.service.LdapCacheService;
038import org.slf4j.Logger;
039import org.slf4j.LoggerFactory;
040import org.springframework.stereotype.Service;
041
042import javax.inject.Inject;
043import javax.inject.Named;
044import javax.naming.NamingEnumeration;
045import javax.naming.NamingException;
046import javax.naming.directory.DirContext;
047import javax.naming.directory.SearchControls;
048import javax.naming.directory.SearchResult;
049
050/**
051 * LdapBindAuthenticator:
052 *
053 * @author: Jesse McConnell
054 */
055@Service( "authenticator#ldap" )
056public class LdapBindAuthenticator
057    extends AbstractAuthenticator
058    implements Authenticator
059{
060
061    private Logger log = LoggerFactory.getLogger( getClass() );
062
063    @Inject
064    @Named( value = "userMapper#ldap" )
065    private UserMapper mapper;
066
067    @Inject
068    @Named( value = "ldapConnectionFactory#configurable" )
069    private LdapConnectionFactory connectionFactory;
070
071    @Inject
072    @Named( value = "userConfiguration#default" )
073    private UserConfiguration config;
074
075    @Inject
076    private LdapCacheService ldapCacheService;
077
078    public String getId()
079    {
080        return "LdapBindAuthenticator";
081    }
082
083    public AuthenticationResult authenticate( AuthenticationDataSource s )
084        throws AuthenticationException
085    {
086        PasswordBasedAuthenticationDataSource source = (PasswordBasedAuthenticationDataSource) s;
087
088        if ( !config.getBoolean( UserConfigurationKeys.LDAP_BIND_AUTHENTICATOR_ENABLED ) || (
089            !config.getBoolean( UserConfigurationKeys.LDAP_BIND_AUTHENTICATOR_ALLOW_EMPTY_PASSWORDS, false )
090                && StringUtils.isEmpty( source.getPassword() ) ) )
091        {
092            return new AuthenticationResult( false, source.getUsername(), null );
093        }
094
095        SearchControls ctls = new SearchControls();
096
097        ctls.setCountLimit( 1 );
098
099        ctls.setDerefLinkFlag( true );
100        ctls.setSearchScope( SearchControls.SUBTREE_SCOPE );
101
102        String filter = "(&(objectClass=" + mapper.getUserObjectClass() + ")" + ( mapper.getUserFilter() != null
103            ? mapper.getUserFilter()
104            : "" ) + "(" + mapper.getUserIdAttribute() + "=" + LdapUtils.encodeFilterValue( source.getUsername() ) + "))";
105
106        log.debug( "Searching for users with filter: '{}' from base dn: {}", filter, mapper.getUserBaseDn() );
107
108        LdapConnection ldapConnection = null;
109        LdapConnection authLdapConnection = null;
110        NamingEnumeration<SearchResult> results = null;
111        try
112        {
113            ldapConnection = getLdapConnection();
114            // check the cache for user's userDn in the ldap server
115            String userDn = ldapCacheService.getLdapUserDn( source.getUsername() );
116
117            if ( userDn == null )
118            {
119                log.debug( "userDn for user {} not found in cache. Retrieving from ldap server..",
120                           source.getUsername() );
121
122                DirContext context = ldapConnection.getDirContext();
123
124                results = context.search( mapper.getUserBaseDn(), filter, ctls );
125
126                boolean moreElements = results.hasMoreElements();
127
128                log.debug( "Found user '{}': {}", source.getUsername(), moreElements );
129
130                if ( moreElements )
131                {
132                    try {
133                        SearchResult result = results.nextElement();
134
135                        userDn = result.getNameInNamespace();
136
137                        log.debug("Adding userDn {} for user {} to the cache..", userDn, source.getUsername());
138
139                        // REDBACK-289/MRM-1488 cache the ldap user's userDn to lessen calls to ldap server
140                        ldapCacheService.addLdapUserDn(source.getUsername(), userDn);
141                    } catch (Exception e) {
142                        log.error("Error occured on LDAP result retrieval: {}, {}", userDn, e.getMessage());
143                        return new AuthenticationResult( false, source.getUsername(), e);
144                    }
145                }
146                else
147                {
148                    return new AuthenticationResult( false, source.getUsername(), null );
149                }
150            }
151
152            log.debug( "Attempting Authenication: {}", userDn );
153
154            authLdapConnection = connectionFactory.getConnection( userDn, source.getPassword() );
155
156            log.info( "user '{}' authenticated", source.getUsername() );
157
158            return new AuthenticationResult( true, source.getUsername(), null );
159        }
160        catch ( LdapException e )
161        {
162            return new AuthenticationResult( false, source.getUsername(), e );
163        }
164        catch ( NamingException e )
165        {
166            return new AuthenticationResult( false, source.getUsername(), e );
167        }
168        finally
169        {
170            closeNamingEnumeration( results );
171            closeLdapConnection( ldapConnection );
172            if ( authLdapConnection != null )
173            {
174                closeLdapConnection( authLdapConnection );
175            }
176        }
177    }
178
179    public boolean supportsDataSource( AuthenticationDataSource source )
180    {
181        return ( source instanceof PasswordBasedAuthenticationDataSource );
182    }
183
184    private LdapConnection getLdapConnection()
185        throws LdapException
186    {
187        return connectionFactory.getConnection();
188    }
189
190    private void closeLdapConnection( LdapConnection ldapConnection )
191    {
192        if ( ldapConnection != null )
193        {
194            try
195            {
196                ldapConnection.close();
197            }
198            catch ( NamingException e )
199            {
200                log.error( "Could not close connection: {}", e.getMessage( ), e );
201            }
202        }
203    }
204
205    private void closeNamingEnumeration( NamingEnumeration<SearchResult> results )
206    {
207        try
208        {
209            if ( results != null )
210            {
211                results.close();
212            }
213        }
214        catch ( NamingException e )
215        {
216            log.warn( "skip exception closing naming search result {}", e.getMessage() );
217        }
218    }
219
220    @Override
221    public boolean isValid() {
222        return connectionFactory.isValid();
223    }
224}