001    package 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    
021    import org.apache.archiva.admin.model.RepositoryAdminException;
022    import org.apache.archiva.admin.model.runtime.RedbackRuntimeConfigurationAdmin;
023    import org.apache.archiva.redback.authentication.AbstractAuthenticator;
024    import org.apache.archiva.redback.authentication.AuthenticationConstants;
025    import org.apache.archiva.redback.authentication.AuthenticationDataSource;
026    import org.apache.archiva.redback.authentication.AuthenticationException;
027    import org.apache.archiva.redback.authentication.AuthenticationFailureCause;
028    import org.apache.archiva.redback.authentication.AuthenticationResult;
029    import org.apache.archiva.redback.authentication.Authenticator;
030    import org.apache.archiva.redback.authentication.PasswordBasedAuthenticationDataSource;
031    import org.apache.archiva.redback.policy.AccountLockedException;
032    import org.apache.archiva.redback.policy.MustChangePasswordException;
033    import org.apache.archiva.redback.policy.PasswordEncoder;
034    import org.apache.archiva.redback.policy.UserSecurityPolicy;
035    import org.apache.archiva.redback.users.User;
036    import org.apache.archiva.redback.users.UserManager;
037    import org.apache.archiva.redback.users.UserManagerException;
038    import org.apache.archiva.redback.users.UserNotFoundException;
039    import org.slf4j.Logger;
040    import org.slf4j.LoggerFactory;
041    import org.springframework.context.ApplicationContext;
042    import org.springframework.stereotype.Service;
043    
044    import javax.annotation.PostConstruct;
045    import javax.inject.Inject;
046    import java.util.ArrayList;
047    import java.util.List;
048    
049    /**
050     * @author Olivier Lamy
051     * @since 1.4-M4
052     */
053    @Service("authenticator#archiva")
054    public class ArchivaUserManagerAuthenticator
055        extends AbstractAuthenticator
056        implements Authenticator
057    {
058        private Logger log = LoggerFactory.getLogger( getClass() );
059    
060        @Inject
061        private UserSecurityPolicy securityPolicy;
062    
063        @Inject
064        private ApplicationContext applicationContext;
065    
066        @Inject
067        private RedbackRuntimeConfigurationAdmin redbackRuntimeConfigurationAdmin;
068    
069        private List<UserManager> userManagers;
070    
071        @PostConstruct
072        @Override
073        public void initialize()
074            throws AuthenticationException
075        {
076            try
077            {
078                List<String> userManagerImpls =
079                    redbackRuntimeConfigurationAdmin.getRedbackRuntimeConfiguration().getUserManagerImpls();
080    
081                userManagers = new ArrayList<UserManager>( userManagerImpls.size() );
082    
083                for ( String beanId : userManagerImpls )
084                {
085                    userManagers.add( applicationContext.getBean( "userManager#" + beanId, UserManager.class ) );
086                }
087            }
088            catch ( RepositoryAdminException e )
089            {
090                throw new AuthenticationException( e.getMessage(), e );
091            }
092        }
093    
094    
095        public AuthenticationResult authenticate( AuthenticationDataSource ds )
096            throws AuthenticationException, AccountLockedException, MustChangePasswordException
097        {
098            boolean authenticationSuccess = false;
099            String username = null;
100            Exception resultException = null;
101            PasswordBasedAuthenticationDataSource source = (PasswordBasedAuthenticationDataSource) ds;
102            List<AuthenticationFailureCause> authnResultErrors = new ArrayList<AuthenticationFailureCause>();
103    
104            for ( UserManager userManager : userManagers )
105            {
106                try
107                {
108                    log.debug( "Authenticate: {} with userManager: {}", source, userManager.getId() );
109                    User user = userManager.findUser( source.getUsername() );
110                    username = user.getUsername();
111    
112                    if ( user.isLocked() )
113                    {
114                        //throw new AccountLockedException( "Account " + source.getUsername() + " is locked.", user );
115                        AccountLockedException e =
116                            new AccountLockedException( "Account " + source.getUsername() + " is locked.", user );
117                        log.warn( "{}", e.getMessage() );
118                        resultException = e;
119                        authnResultErrors.add(
120                            new AuthenticationFailureCause( AuthenticationConstants.AUTHN_LOCKED_USER_EXCEPTION,
121                                                            e.getMessage() ) );
122                    }
123    
124                    if ( user.isPasswordChangeRequired() && source.isEnforcePasswordChange() )
125                    {
126                        //throw new MustChangePasswordException( "Password expired.", user );
127                        MustChangePasswordException e = new MustChangePasswordException( "Password expired.", user );
128                        log.warn( "{}", e.getMessage() );
129                        resultException = e;
130                        authnResultErrors.add(
131                            new AuthenticationFailureCause( AuthenticationConstants.AUTHN_MUST_CHANGE_PASSWORD_EXCEPTION,
132                                                            e.getMessage() ) );
133                    }
134    
135                    PasswordEncoder encoder = securityPolicy.getPasswordEncoder();
136                    log.debug( "PasswordEncoder: {}", encoder.getClass().getName() );
137    
138                    boolean isPasswordValid = encoder.isPasswordValid( user.getEncodedPassword(), source.getPassword() );
139                    if ( isPasswordValid )
140                    {
141                        log.debug( "User {} provided a valid password", source.getUsername() );
142    
143                        try
144                        {
145                            securityPolicy.extensionPasswordExpiration( user );
146    
147                            authenticationSuccess = true;
148    
149                            //REDBACK-151 do not make unnessesary updates to the user object
150                            if ( user.getCountFailedLoginAttempts() > 0 )
151                            {
152                                user.setCountFailedLoginAttempts( 0 );
153                                if ( !userManager.isReadOnly() )
154                                {
155                                    userManager.updateUser( user );
156                                }
157                            }
158    
159                            return new AuthenticationResult( true, source.getUsername(), null );
160                        }
161                        catch ( MustChangePasswordException e )
162                        {
163                            user.setPasswordChangeRequired( true );
164                            //throw e;
165                            resultException = e;
166                            authnResultErrors.add( new AuthenticationFailureCause(
167                                AuthenticationConstants.AUTHN_MUST_CHANGE_PASSWORD_EXCEPTION, e.getMessage() ).user( user ) );
168                        }
169                    }
170                    else
171                    {
172                        log.warn( "Password is Invalid for user {} and userManager '{}'.", source.getUsername(),
173                                  userManager.getId() );
174                        authnResultErrors.add( new AuthenticationFailureCause( AuthenticationConstants.AUTHN_NO_SUCH_USER,
175                                                                               "Password is Invalid for user "
176                                                                                   + source.getUsername() + "." ).user( user ) );
177    
178                        try
179                        {
180    
181                            securityPolicy.extensionExcessiveLoginAttempts( user );
182    
183                        }
184                        finally
185                        {
186                            if ( !userManager.isReadOnly() )
187                            {
188                                userManager.updateUser( user );
189                            }
190                        }
191    
192                        //return new AuthenticationResult( false, source.getUsername(), null, authnResultExceptionsMap );
193                    }
194                }
195                catch ( UserNotFoundException e )
196                {
197                    log.warn( "Login for user {} and userManager {} failed. user not found.", source.getUsername(),
198                              userManager.getId() );
199                    resultException = e;
200                    authnResultErrors.add( new AuthenticationFailureCause( AuthenticationConstants.AUTHN_NO_SUCH_USER,
201                                                                           "Login for user " + source.getUsername()
202                                                                               + " failed. user not found." ) );
203                }
204                catch ( Exception e )
205                {
206                    log.warn( "Login for user {} and userManager {} failed, message: {}", source.getUsername(),
207                              userManager.getId(), e.getMessage() );
208                    resultException = e;
209                    authnResultErrors.add( new AuthenticationFailureCause( AuthenticationConstants.AUTHN_RUNTIME_EXCEPTION,
210                                                                           "Login for user " + source.getUsername()
211                                                                               + " failed, message: " + e.getMessage() ) );
212                }
213            }
214            return new AuthenticationResult( authenticationSuccess, username, resultException, authnResultErrors );
215        }
216    
217        public boolean supportsDataSource( AuthenticationDataSource source )
218        {
219            return ( source instanceof PasswordBasedAuthenticationDataSource );
220        }
221    
222        public String getId()
223        {
224            return "ArchivaUserManagerAuthenticator";
225        }
226    }