001package org.apache.archiva.web.security; 002/* 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, 014 * software distributed under the License is distributed on an 015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 016 * KIND, either express or implied. See the License for the 017 * specific language governing permissions and limitations 018 * under the License. 019 */ 020 021import org.apache.archiva.admin.model.RepositoryAdminException; 022import org.apache.archiva.admin.model.runtime.RedbackRuntimeConfigurationAdmin; 023import org.apache.archiva.metadata.model.facets.AuditEvent; 024import org.apache.archiva.redback.authentication.AbstractAuthenticator; 025import org.apache.archiva.redback.authentication.AuthenticationConstants; 026import org.apache.archiva.redback.authentication.AuthenticationDataSource; 027import org.apache.archiva.redback.authentication.AuthenticationException; 028import org.apache.archiva.redback.authentication.AuthenticationFailureCause; 029import org.apache.archiva.redback.authentication.AuthenticationResult; 030import org.apache.archiva.redback.authentication.Authenticator; 031import org.apache.archiva.redback.authentication.PasswordBasedAuthenticationDataSource; 032import org.apache.archiva.redback.policy.AccountLockedException; 033import org.apache.archiva.redback.policy.MustChangePasswordException; 034import org.apache.archiva.redback.policy.PasswordEncoder; 035import org.apache.archiva.redback.policy.UserSecurityPolicy; 036import org.apache.archiva.redback.users.User; 037import org.apache.archiva.redback.users.UserManager; 038import org.apache.archiva.redback.users.UserNotFoundException; 039import org.apache.archiva.repository.events.AuditListener; 040import org.apache.archiva.rest.services.interceptors.AuditInfoFilter; 041import org.slf4j.Logger; 042import org.slf4j.LoggerFactory; 043import org.springframework.context.ApplicationContext; 044import org.springframework.stereotype.Service; 045 046import javax.annotation.PostConstruct; 047import javax.inject.Inject; 048import java.util.ArrayList; 049import java.util.List; 050 051/** 052 * @author Olivier Lamy 053 * @since 1.4-M4 054 */ 055@Service("authenticator#archiva") 056public class ArchivaUserManagerAuthenticator 057 extends AbstractAuthenticator 058 implements Authenticator 059{ 060 private Logger log = LoggerFactory.getLogger( getClass() ); 061 062 @Inject 063 private UserSecurityPolicy securityPolicy; 064 065 @Inject 066 private ApplicationContext applicationContext; 067 068 @Inject 069 private RedbackRuntimeConfigurationAdmin redbackRuntimeConfigurationAdmin; 070 071 @Inject 072 private List<AuditListener> auditListeners = new ArrayList<>(); 073 074 private List<UserManager> userManagers; 075 076 private boolean valid = false; 077 078 @PostConstruct 079 @Override 080 public void initialize() 081 throws AuthenticationException 082 { 083 try 084 { 085 List<String> userManagerImpls = 086 redbackRuntimeConfigurationAdmin.getRedbackRuntimeConfiguration().getUserManagerImpls(); 087 088 userManagers = new ArrayList<>( userManagerImpls.size() ); 089 090 for ( String beanId : userManagerImpls ) 091 { 092 userManagers.add( applicationContext.getBean( "userManager#" + beanId, UserManager.class ) ); 093 } 094 valid=true; 095 } 096 catch ( RepositoryAdminException e ) 097 { 098 log.error("Error during repository initialization "+e.getMessage(),e); 099 // throw new AuthenticationException( e.getMessage(), e ); 100 } 101 } 102 103 protected AuditInfoFilter.AuditInfo getAuditInformation() 104 { 105 return AuditInfoFilter.getAuditInfo( ); 106 } 107 108 public List<AuditListener> getAuditListeners() 109 { 110 return auditListeners; 111 } 112 113 protected void triggerAuditEvent( String repositoryId, String filePath, String action, String user ) 114 { 115 AuditEvent auditEvent = new AuditEvent( repositoryId, user, filePath, action ); 116 AuditInfoFilter.AuditInfo auditInformation = getAuditInformation(); 117 auditEvent.setUserId( user ); 118 auditEvent.setRemoteIP( auditInformation.getRemoteHost() + ":" + auditInformation.getRemotePort() ); 119 for ( AuditListener auditListener : getAuditListeners() ) 120 { 121 auditListener.auditEvent( auditEvent ); 122 } 123 } 124 125 @Override 126 public AuthenticationResult authenticate( AuthenticationDataSource ds ) 127 throws AuthenticationException, AccountLockedException, MustChangePasswordException 128 { 129 boolean authenticationSuccess = false; 130 String username = null; 131 Exception resultException = null; 132 PasswordBasedAuthenticationDataSource source = (PasswordBasedAuthenticationDataSource) ds; 133 List<AuthenticationFailureCause> authnResultErrors = new ArrayList<>(); 134 final String loginUserId = source.getUsername( ); 135 136 for ( UserManager userManager : userManagers ) 137 { 138 try 139 { 140 log.debug( "Authenticate: {} with userManager: {}", source, userManager.getId() ); 141 User user = userManager.findUser( loginUserId ); 142 username = user.getUsername(); 143 144 if ( user.isLocked() ) 145 { 146 //throw new AccountLockedException( "Account " + source.getUsername() + " is locked.", user ); 147 AccountLockedException e = 148 new AccountLockedException( "Account " + loginUserId + " is locked.", user ); 149 log.warn( "{}", e.getMessage() ); 150 triggerAuditEvent( "", "", "login-account-locked", loginUserId ); 151 resultException = e; 152 authnResultErrors.add( 153 new AuthenticationFailureCause( AuthenticationConstants.AUTHN_LOCKED_USER_EXCEPTION, 154 e.getMessage() ) ); 155 } 156 157 if ( user.isPasswordChangeRequired() && source.isEnforcePasswordChange() ) 158 { 159 //throw new MustChangePasswordException( "Password expired.", user ); 160 MustChangePasswordException e = new MustChangePasswordException( "Password expired.", user ); 161 log.warn( "{}", e.getMessage() ); 162 resultException = e; 163 triggerAuditEvent( "", "", "login-password-change-required", loginUserId ); 164 authnResultErrors.add( 165 new AuthenticationFailureCause( AuthenticationConstants.AUTHN_MUST_CHANGE_PASSWORD_EXCEPTION, 166 e.getMessage() ) ); 167 } 168 169 PasswordEncoder encoder = securityPolicy.getPasswordEncoder(); 170 log.debug( "PasswordEncoder: {}", encoder.getClass().getName() ); 171 172 boolean isPasswordValid = encoder.isPasswordValid( user.getEncodedPassword(), source.getPassword() ); 173 if ( isPasswordValid ) 174 { 175 log.debug( "User {} provided a valid password", loginUserId ); 176 177 try 178 { 179 securityPolicy.extensionPasswordExpiration( user ); 180 181 authenticationSuccess = true; 182 triggerAuditEvent( "", "", "login-success", loginUserId ); 183 184 185 //REDBACK-151 do not make unnessesary updates to the user object 186 if ( user.getCountFailedLoginAttempts() > 0 ) 187 { 188 user.setCountFailedLoginAttempts( 0 ); 189 if ( !userManager.isReadOnly() ) 190 { 191 userManager.updateUser( user ); 192 } 193 } 194 195 return new AuthenticationResult( true, loginUserId, null ); 196 } 197 catch ( MustChangePasswordException e ) 198 { 199 user.setPasswordChangeRequired( true ); 200 triggerAuditEvent( "", "", "login-password-change-required", loginUserId ); 201 //throw e; 202 resultException = e; 203 authnResultErrors.add( new AuthenticationFailureCause( 204 AuthenticationConstants.AUTHN_MUST_CHANGE_PASSWORD_EXCEPTION, e.getMessage() ).user( user ) ); 205 } 206 } 207 else 208 { 209 log.warn( "Password is Invalid for user {} and userManager '{}'.", source.getUsername(), 210 userManager.getId() ); 211 triggerAuditEvent( "", "", "login-authentication-failed", loginUserId ); 212 213 authnResultErrors.add( new AuthenticationFailureCause( AuthenticationConstants.AUTHN_NO_SUCH_USER, 214 "Password is Invalid for user " 215 + source.getUsername() + "." ).user( user ) ); 216 217 try 218 { 219 220 securityPolicy.extensionExcessiveLoginAttempts( user ); 221 222 } 223 finally 224 { 225 if ( !userManager.isReadOnly() ) 226 { 227 userManager.updateUser( user ); 228 } 229 } 230 231 //return new AuthenticationResult( false, source.getUsername(), null, authnResultExceptionsMap ); 232 } 233 } 234 catch ( UserNotFoundException e ) 235 { 236 log.warn( "Login for user {} and userManager {} failed. user not found.", loginUserId, 237 userManager.getId() ); 238 resultException = e; 239 triggerAuditEvent( "", "", "login-user-unknown", loginUserId ); 240 authnResultErrors.add( new AuthenticationFailureCause( AuthenticationConstants.AUTHN_NO_SUCH_USER, 241 "Login for user " + source.getUsername() 242 + " failed. user not found." ) ); 243 } 244 catch ( Exception e ) 245 { 246 log.warn( "Login for user {} and userManager {} failed, message: {}", loginUserId, 247 userManager.getId(), e.getMessage() ); 248 resultException = e; 249 triggerAuditEvent( "", "", "login-error", loginUserId ); 250 authnResultErrors.add( new AuthenticationFailureCause( AuthenticationConstants.AUTHN_RUNTIME_EXCEPTION, 251 "Login for user " + source.getUsername() 252 + " failed, message: " + e.getMessage() ) ); 253 } 254 } 255 return new AuthenticationResult( authenticationSuccess, username, resultException, authnResultErrors ); 256 } 257 258 @Override 259 public boolean supportsDataSource( AuthenticationDataSource source ) 260 { 261 return ( source instanceof PasswordBasedAuthenticationDataSource ); 262 } 263 264 @Override 265 public String getId() 266 { 267 return "ArchivaUserManagerAuthenticator"; 268 } 269 270 public boolean isValid() { 271 return valid; 272 } 273}