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}