/* Copyright 2006 - 2010 Under Dusken Licensed 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 no.dusken.aranea.service; import no.dusken.aranea.model.LoginDetails; import no.dusken.common.service.impl.GenericServiceImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.dao.support.DataAccessUtils; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.encoding.PasswordEncoder; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @author Marvin B. Lillehaug <lillehau@underdusken.no> */ @Service("loginDetailsService") @Transactional(propagation = Propagation.SUPPORTS, isolation = Isolation.DEFAULT, readOnly = true) public class LoginDetailsServiceImpl extends GenericServiceImpl<LoginDetails> implements LoginDetailsService { private AuthenticationManager authenticationManager; private PasswordEncoder encoder; private static final Logger logger = LoggerFactory.getLogger(LoginDetailsServiceImpl.class); public LoginDetailsServiceImpl() { super(LoginDetails.class); } /** * Locates the user based on the username. In the actual implementation, the search may possibly be case * insensitive, or case insensitive depending on how the implementaion instance is configured. In this case, the * <code>UserDetails</code> object that comes back may have a username that is of a different case than what was * actually requested.. * * @param username the username presented to the {org.springframework.security.providers.dao.DaoAuthenticationProvider} * @return a fully populated user record (never <code>null</code>) * @throws org.springframework.security.userdetails.UsernameNotFoundException * if the user could not be found or the user has no GrantedAuthority * @throws org.springframework.dao.DataAccessException * if user could not be found for a repository-specific reason */ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { UserDetails ud = null; try { Map<String, Object> map = new HashMap<String, Object>(); map.put("username", username.toLowerCase()); logger.debug("loading logindetail for username: {}", username); List<LoginDetails> list = genericDao.getByNamedQuery("logindetail_byusername", map); ud = (LoginDetails) DataAccessUtils.uniqueResult(list); } catch (DataAccessException dae) { log.warn("Unable to get UserDetails", dae); } if (ud == null) throw new UsernameNotFoundException("User '"+ username +"' not found"); return ud; } /** * Create a new user with the supplied details. * The password should be in plaintext in this object, will be encrypted before persisted. */ public void createUser(UserDetails user) { throw new UnsupportedOperationException("not implemented"); } /** * Create a new user with the supplied details. * The password should be in plaintext in this object, will be encrypted before persisted. */ @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false) public LoginDetails createLoginDetails(LoginDetails user) { String plainText = user.getPassword(); String encrypted = encoder.encodePassword(plainText, user.getUsername()); user.setPassword(encrypted); logger.info("Creating user with username: {}", user.getUsername()); return super.saveOrUpdate(user); } @Override public LoginDetails saveOrUpdate(LoginDetails user){ if(user.getID() == null){// this is a new user, save it. return createLoginDetails(user); }else{//user exists, update it return updateLoginDetails(user); } } /** * Update the specified user. */ public void updateUser(UserDetails user) { LoginDetails userDetails = (LoginDetails) user; super.saveOrUpdate(userDetails); } public LoginDetails updateLoginDetails(LoginDetails user){ return super.saveOrUpdate(user); } /** * Remove the user with the given login name from the system. */ public void deleteUser(String username) { LoginDetails user = (LoginDetails) loadUserByUsername(username); remove(user); } /** * Modify the current user's password. This should change the user's password in * the persistent user repository (datbase, LDAP etc) and should also modify the * current security context to contain the new password. * * @param oldPassword current password (for re-authentication if required) * @param newPassword the password to change to */ public void changePassword(String oldPassword, String newPassword) { Authentication currentUser = SecurityContextHolder.getContext().getAuthentication(); if (currentUser == null) { // This would indicate bad coding somewhere throw new AccessDeniedException("Can't change password as no Authentication object found in context " + "for current user."); } String username = currentUser.getName(); LoginDetails user = (LoginDetails) loadUserByUsername(username); // If an authentication manager has been set, reauthenticate the user with the supplied password. if (authenticationManager != null) { logger.info("Reauthenticating user '{}' for password change request.", username); authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, oldPassword)); } else { logger.debug("No authentication manager set. Password won't be re-checked."); } logger.info("Changing password for user '{}'", username); String encoded = encoder.encodePassword(newPassword, username); user.setPassword(encoded); super.saveOrUpdate(user); //reauthenticating with the new password. UsernamePasswordAuthenticationToken newAuthentication = new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities()); newAuthentication.setDetails(currentUser.getDetails()); SecurityContextHolder.getContext().setAuthentication(newAuthentication); } /** * set the user with new password. the password in the object is in cleartext. * @param user - the user that is reseted, with new password set. */ public void resetPassword(LoginDetails user){ String encoded = encoder.encodePassword(user.getPassword(), user.getUsername()); user.setPassword(encoded); super.saveOrUpdate(user); } /** * Check if a user with the supplied login name exists in the system. */ public boolean userExists(String username) { return loadUserByUsername(username) != null; } @Autowired public void setEncoder(PasswordEncoder encoder) { this.encoder = encoder; } @Autowired public void setAuthenticationManager(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } }