/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.syncope.core.security;

import java.util.Date;
import javax.annotation.Resource;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.syncope.common.types.AuditElements;
import org.apache.syncope.common.types.AuditElements.Result;
import org.apache.syncope.common.types.CipherAlgorithm;
import org.apache.syncope.core.audit.AuditManager;
import org.apache.syncope.core.persistence.beans.SyncopeConf;
import org.apache.syncope.core.persistence.beans.user.SyncopeUser;
import org.apache.syncope.core.persistence.dao.ConfDAO;
import org.apache.syncope.core.persistence.dao.UserDAO;
import org.apache.syncope.core.util.PasswordEncoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.transaction.annotation.Transactional;

@Configurable
public class SyncopeAuthenticationProvider implements AuthenticationProvider {

    /**
     * Logger.
     */
    protected static final Logger LOG = LoggerFactory.getLogger(SyncopeAuthenticationProvider.class);

    @Autowired
    private AuditManager auditManager;

    @Autowired
    private ConfDAO confDAO;

    @Autowired
    private UserDAO userDAO;

    @Resource(name = "adminUser")
    private String adminUser;

    private String adminPassword;

    private String adminPasswordAlgorithm;

    private SyncopeUserDetailsService userDetailsService;

    /**
     * @param adminPassword the adminPassword to set
     */
    public void setAdminPassword(final String adminPassword) {
        this.adminPassword = adminPassword;
    }

    /**
     * @param adminPasswordAlgorithm the adminPasswordAlgorithm to set
     */
    public void setAdminPasswordAlgorithm(final String adminPasswordAlgorithm) {
        this.adminPasswordAlgorithm = adminPasswordAlgorithm;
    }

    public void setSyncopeUserDetailsService(final SyncopeUserDetailsService syncopeUserDetailsService) {
        this.userDetailsService = syncopeUserDetailsService;
    }

    @Override
    @Transactional(noRollbackFor = { BadCredentialsException.class, DisabledException.class })
    public Authentication authenticate(final Authentication authentication)
            throws AuthenticationException {

        boolean authenticated = false;
        SyncopeUser user = null;

        String username = authentication.getName();
        if (adminUser.equals(username)) {
            authenticated = authenticate(
                    authentication.getCredentials().toString(),
                    CipherAlgorithm.valueOf(adminPasswordAlgorithm),
                    adminPassword);
        } else {
            user = userDAO.find(username);

            if (user != null) {
                if (user.isSuspended() != null && user.isSuspended()) {
                    throw new DisabledException("User " + user.getUsername() + " is suspended");
                }

                SyncopeConf authStatuses = confDAO.find("authentication.statuses", null);
                if (authStatuses != null) {
                    String[] statuses = authStatuses.getValue().split("\\|");
                    if (!ArrayUtils.contains(statuses, user.getStatus())) {
                        throw new DisabledException("User " + user.getUsername() + " not allowed to authenticate");
                    }
                }

                authenticated = authenticate(
                        authentication.getCredentials().toString(),
                        user.getCipherAlgorithm(),
                        user.getPassword());
            }
        }

        updateLoginAttributes(user, authenticated);
        
        UsernamePasswordAuthenticationToken token;

        if (authenticated) {
            token = new UsernamePasswordAuthenticationToken(
                    authentication.getPrincipal(),
                    null,
                    userDetailsService.loadUserByUsername(authentication.getPrincipal().toString()).getAuthorities());

            token.setDetails(authentication.getDetails());

            auditManager.audit(
                    AuditElements.EventCategoryType.REST,
                    "AuthenticationController",
                    null,
                    "login",
                    Result.SUCCESS,
                    null,
                    authenticated,
                    authentication,
                    "Successfully authenticated, with roles: " + token.getAuthorities());

            LOG.debug("User {} successfully authenticated, with roles {}",
                    authentication.getPrincipal(), token.getAuthorities());

        } else {
            auditManager.audit(
                    AuditElements.EventCategoryType.REST,
                    "AuthenticationController",
                    null,
                    "login",
                    Result.FAILURE,
                    null,
                    authenticated,
                    authentication,
                    "User " + authentication.getPrincipal() + " not authenticated");

            LOG.debug("User {} not authenticated", authentication.getPrincipal());

            throw new BadCredentialsException("User " + authentication.getPrincipal() + " not authenticated");
        }

        return token;
    }
    
    private void updateLoginAttributes(SyncopeUser user, boolean authenticated) {
    	
    	if (user != null) {
    		boolean userModified = false;
    		
    		if (Boolean.valueOf(confDAO.find("log.lastlogindate", Boolean.toString(true)).getValue())) {
    			user.setLastLoginDate(new Date());
    			userModified = true;
    		}
    		
    		if (authenticated) {
    			if (user.getFailedLogins() != 0) {
    				user.setFailedLogins(0);
    				userModified = true;
    			}
    		} else {
    			user.setFailedLogins(user.getFailedLogins() + 1);
    			userModified = true;
    		}
    		
    		if (userModified) {
    			userDAO.save(user);
    		}
    	}
    }

    protected boolean authenticate(final String password, final CipherAlgorithm cipherAlgorithm,
            final String digestedPassword) {

        return PasswordEncoder.verify(password, cipherAlgorithm, digestedPassword);
    }

    @Override
    public boolean supports(final Class<? extends Object> type) {
        return type.equals(UsernamePasswordAuthenticationToken.class);
    }
}

@@ -6747,16 +6747,44 @@
    %09%09if 
+(authenticated) %7B%0A    %09%09%09if 
 (Boolean
@@ -6865,24 +6865,28 @@
 e())) %7B%0A    
+   
+ 
 %09%09%09user.setL
@@ -6907,24 +6907,28 @@
 ew Date());%0A
+    
     %09%09%09userM
@@ -6951,45 +6951,23 @@
     
-%09%09%7D%0A
     %09%09
+%7D
 %0A    %09%09
-if (authenticated) %7B
+%09
 %0A   

Fix for implementation: set last login date only on successful authentication