001package org.apache.archiva.redback.authentication; 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.configuration.UserConfiguration; 023import org.apache.archiva.redback.policy.AccountLockedException; 024import org.apache.archiva.redback.policy.MustChangePasswordException; 025import org.apache.archiva.redback.users.User; 026import org.apache.archiva.redback.users.UserManager; 027import org.apache.archiva.redback.users.UserManagerException; 028import org.slf4j.Logger; 029import org.slf4j.LoggerFactory; 030import org.springframework.context.ApplicationContext; 031import org.springframework.stereotype.Service; 032 033import javax.annotation.PostConstruct; 034import javax.inject.Inject; 035import javax.inject.Named; 036import java.util.ArrayList; 037import java.util.Collection; 038import java.util.Collections; 039import java.util.HashMap; 040import java.util.LinkedHashMap; 041import java.util.List; 042import java.util.Map; 043import java.util.Spliterator; 044import java.util.Spliterators; 045import java.util.concurrent.atomic.AtomicReference; 046import java.util.stream.Collectors; 047import java.util.stream.Stream; 048import java.util.stream.StreamSupport; 049 050 051/** 052 * DefaultAuthenticationManager: the goal of the authentication manager is to act as a conduit for 053 * authentication requests into different authentication schemes 054 * <p> 055 * For example, the default implementation can be configured with any number of authenticators and will 056 * sequentially try them for an authenticated result. This allows you to have the standard user/pass 057 * auth procedure followed by authentication based on a known key for 'remember me' type functionality. 058 * 059 * @author: Jesse McConnell 060 */ 061@Service( "authenticationManager" ) 062public class DefaultAuthenticationManager 063 implements AuthenticationManager 064{ 065 066 private static final Logger log = LoggerFactory.getLogger( DefaultAuthenticationManager.class ); 067 068 final private AtomicReference<List<Authenticator>> authenticators = new AtomicReference<>( ); 069 final private AtomicReference<Map<String, AuthenticatorControl>> controls = new AtomicReference<>( ); 070 final private AtomicReference<Map<String, AuthenticatorControl>> modfiedControls = new AtomicReference<>( ); 071 private Map<String, Authenticator> availableAuthenticators; 072 073 @Inject 074 private ApplicationContext applicationContext; 075 076 @Inject 077 @Named("userConfiguration#default") 078 private UserConfiguration userConfiguration; 079 080 @Inject 081 @Named( "userManager#default" ) 082 private UserManager userManager; 083 084 @SuppressWarnings( "unchecked" ) 085 @PostConstruct 086 public void initialize( ) 087 { 088 this.availableAuthenticators = 089 applicationContext.getBeansOfType( Authenticator.class ).values( ).stream( ).collect( Collectors.toMap( a -> a.getId( ), authenticator -> authenticator ) ); 090 this.modfiedControls.set( new HashMap<>( ) ); 091 initializeOrder( ); 092 } 093 094 private void initializeOrder() { 095 Stream<AuthenticatorControl> controlStream = initControls( ); 096 final List<Authenticator> authenticators = new ArrayList<>( ); 097 final Map<String, AuthenticatorControl> controls = new LinkedHashMap<>( ); 098 controlStream.forEachOrdered( control -> { 099 authenticators.add( this.availableAuthenticators.get( control.getName( ) ) ); 100 controls.put( control.getName( ), control ); 101 } ); 102 this.authenticators.set( authenticators ); 103 this.controls.set( controls ); 104 } 105 106 Map<String, AuthenticatorControl> getConfigControls( ) 107 { 108 return new HashMap<>( ); 109 } 110 111 Map<String, Authenticator> getAvailableAuthenticators() { 112 return this.availableAuthenticators; 113 } 114 115 Map<String, AuthenticatorControl> getModifiedControls() { 116 return this.modfiedControls.get(); 117 } 118 119 void setModfiedControls(Map<String, AuthenticatorControl> newControls) { 120 this.modfiedControls.set( newControls ); 121 } 122 123 Map<String, AuthenticatorControl> getControlMap() { 124 return controls.get(); 125 } 126 127 128 private Stream<AuthenticatorControl> initControls( ) 129 { 130 Collection<Authenticator> authenticators = getAvailableAuthenticators().values(); 131 Map<String, AuthenticatorControl> nondefault = getModifiedControls( ); 132 Map<String, AuthenticatorControl> configControlMap = getConfigControls( ); 133 Spliterator<Authenticator> spliterator = Spliterators.spliterator( authenticators, Spliterator.NONNULL ); 134 return StreamSupport.stream( spliterator, false ).map( authenticator -> { 135 final String id = authenticator.getId( ); 136 return nondefault.containsKey( id ) ? nondefault.get(id) : (configControlMap.containsKey( id ) ? configControlMap.get( id ) : new AuthenticatorControl( id, 100, AuthenticationControl.SUFFICIENT, true )); 137 } ).sorted( Collections.reverseOrder( ) ); 138 } 139 140 141 public String getId( ) 142 { 143 return "Default Authentication Manager - " + this.getClass( ).getName( ) + " : managed authenticators - " + 144 knownAuthenticators( ); 145 } 146 147 public AuthenticationResult authenticate( AuthenticationDataSource source ) 148 throws AccountLockedException, AuthenticationException, MustChangePasswordException 149 { 150 List<Authenticator> authenticators = this.authenticators.get( ); 151 if ( authenticators == null || authenticators.size( ) == 0 ) 152 { 153 return ( new AuthenticationResult( false, null, new AuthenticationException( 154 "no valid authenticators, can't authenticate" ) ) ); 155 } 156 157 // put AuthenticationResult exceptions in a map 158 List<AuthenticationFailureCause> authnResultErrors = new ArrayList<AuthenticationFailureCause>( ); 159 for ( Authenticator authenticator : authenticators ) 160 { 161 final AuthenticatorControl control = getControlMap( ).get( authenticator.getId( ) ); 162 assert control != null; 163 if ( authenticator.isValid( ) && control.isActive()) 164 { 165 if ( authenticator.supportsDataSource( source ) ) 166 { 167 AuthenticationResult authResult = authenticator.authenticate( source ); 168 List<AuthenticationFailureCause> authenticationFailureCauses = 169 authResult.getAuthenticationFailureCauses( ); 170 171 if ( authResult.isAuthenticated( ) ) 172 { 173 //olamy: as we can chain various user managers with Archiva 174 // user manager authenticator can lock accounts in the following case : 175 // 2 user managers: ldap and jdo. 176 // ldap correctly find the user but cannot compare hashed password 177 // jdo reject password so increase loginAttemptCount 178 // now ldap bind authenticator work but loginAttemptCount has been increased. 179 // so we restore here loginAttemptCount to 0 if in authenticationFailureCauses 180 181 for ( AuthenticationFailureCause authenticationFailureCause : authenticationFailureCauses ) 182 { 183 User user = authenticationFailureCause.getUser( ); 184 if ( user != null ) 185 { 186 if ( user.getCountFailedLoginAttempts( ) > 0 ) 187 { 188 user.setCountFailedLoginAttempts( 0 ); 189 if ( !userManager.isReadOnly( ) ) 190 { 191 try 192 { 193 userManager.updateUser( user ); 194 } 195 catch ( UserManagerException e ) 196 { 197 log.debug( e.getMessage( ), e ); 198 log.warn( "skip error updating user: {}", e.getMessage( ) ); 199 } 200 } 201 } 202 } 203 } 204 return authResult; 205 } 206 207 if ( authenticationFailureCauses != null ) 208 { 209 authnResultErrors.addAll( authenticationFailureCauses ); 210 } 211 else 212 { 213 if ( authResult.getException( ) != null ) 214 { 215 authnResultErrors.add( 216 new AuthenticationFailureCause( AuthenticationConstants.AUTHN_RUNTIME_EXCEPTION, 217 authResult.getException( ).getMessage( ) ) ); 218 } 219 } 220 221 222 } 223 } 224 else 225 { 226 log.warn( "Invalid authenticator found: " + authenticator.getId( ) ); 227 } 228 } 229 230 return ( new AuthenticationResult( false, null, new AuthenticationException( 231 "authentication failed on authenticators: " + knownAuthenticators( ) ), authnResultErrors ) ); 232 } 233 234 @Override 235 public List<AuthenticatorControl> getControls( ) 236 { 237 return getControlMap().values().stream().collect( Collectors.toList()); 238 } 239 240 @Override 241 public void setControls( List<AuthenticatorControl> controlList ) 242 { 243 setModfiedControls( controlList.stream( ).collect( Collectors.toMap( c -> c.getName( ), c -> c ) ) ); 244 initializeOrder( ); 245 } 246 247 @Override 248 public void modifyControl( AuthenticatorControl control ) 249 { 250 Map<String, AuthenticatorControl> myControls = getModifiedControls( ); 251 if (availableAuthenticators.containsKey( control.getName() )) { 252 myControls.put( control.getName( ), control ); 253 } else { 254 log.warn( "Cannot modify control for authenticator {}. It does not exist.", control.getName( ) ); 255 } 256 initializeOrder(); 257 } 258 259 public List<Authenticator> getAuthenticators( ) 260 { 261 return authenticators.get(); 262 } 263 264 private String knownAuthenticators( ) 265 { 266 StringBuilder strbuf = new StringBuilder( ); 267 268 for ( Authenticator authenticator : getAuthenticators() ) 269 { 270 strbuf.append( '(' ).append( authenticator.getId( ) ).append( ") " ); 271 } 272 273 return strbuf.toString( ); 274 } 275}