/* Copyright 2008-2013 Josh Drummond This file is part of WebPasswordSafe. WebPasswordSafe is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. WebPasswordSafe is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with WebPasswordSafe; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package net.webpasswordsafe.server.service; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.annotation.Resource; import net.webpasswordsafe.client.remote.LoginService; import net.webpasswordsafe.client.remote.UserService; import net.webpasswordsafe.common.model.Group; import net.webpasswordsafe.common.model.IPLockout; import net.webpasswordsafe.common.model.Password; import net.webpasswordsafe.common.model.Subject; import net.webpasswordsafe.common.model.Template; import net.webpasswordsafe.common.model.User; import net.webpasswordsafe.common.model.UserAuthnTOTP; import net.webpasswordsafe.common.util.Constants.Function; import net.webpasswordsafe.server.ServerSessionUtil; import net.webpasswordsafe.server.dao.GroupDAO; import net.webpasswordsafe.server.dao.IPLockoutDAO; import net.webpasswordsafe.server.dao.PasswordDAO; import net.webpasswordsafe.server.dao.TemplateDAO; import net.webpasswordsafe.server.dao.UserDAO; import net.webpasswordsafe.server.plugin.audit.AuditLogger; import net.webpasswordsafe.server.plugin.authentication.TwoStepTOTPAuthenticator; import net.webpasswordsafe.server.plugin.authorization.Authorizer; import net.webpasswordsafe.server.plugin.encryption.Digester; import net.webpasswordsafe.server.plugin.encryption.Encryptor; import net.webpasswordsafe.server.service.helper.WPSXsrfProtectedServiceServlet; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import static net.webpasswordsafe.common.util.Constants.*; /** * Implementation of User Service * * @author Josh Drummond * */ @Service("userService") public class UserServiceImpl extends WPSXsrfProtectedServiceServlet implements UserService { private static final long serialVersionUID = 2818717240050539864L; private static Logger LOG = Logger.getLogger(UserServiceImpl.class); @Autowired private UserDAO userDAO; @Autowired private GroupDAO groupDAO; @Autowired private IPLockoutDAO ipLockoutDAO; @Autowired private PasswordDAO passwordDAO; @Autowired private TemplateDAO templateDAO; @Resource private Digester digester; @Resource private Encryptor encryptor; @Resource private AuditLogger auditLogger; @Resource private Authorizer authorizer; @Autowired private LoginService loginService; @Override @Transactional(propagation=Propagation.REQUIRED) public void changePassword(String password) { Date now = new Date(); User loggedInUser = getLoggedInUser(); if (null != loggedInUser) { loggedInUser.updateAuthnPasswordValue(digester.digest(password)); userDAO.makePersistent(loggedInUser); auditLogger.log(now, loggedInUser.getUsername(), ServerSessionUtil.getIP(), "change password", "", true, ""); } else { auditLogger.log(now, "", ServerSessionUtil.getIP(), "change password", "", false, "not logged in"); throw new RuntimeException("Not logged in"); } } @Override @Transactional(propagation=Propagation.REQUIRED) public void addUser(User newUser) { Date now = new Date(); User loggedInUser = getLoggedInUser(); if (authorizer.isAuthorized(loggedInUser, Function.ADD_USER.name())) { addUserInternal(newUser); } else { auditLogger.log(now, ServerSessionUtil.getUsername(), ServerSessionUtil.getIP(), "add user", userTarget(newUser), false, "not authorized"); throw new RuntimeException("Not Authorized!"); } } @Transactional(propagation=Propagation.REQUIRED) private void addUserInternal(User newUser) { Date now = new Date(); // create base user User user = new User(); user.setUsername(newUser.getUsername()); user.setFullname(newUser.getFullname()); user.setEmail(newUser.getEmail()); user.setActiveFlag(newUser.isActiveFlag()); user.updateAuthnPasswordValue(digester.digest(newUser.getAuthnPasswordValue())); user.setDateCreated(now); userDAO.makePersistent(user); newUser.setId(user.getId()); // assign user to everyone group Group everyoneGroup = getEveryoneGroup(); everyoneGroup.addUser(user); // assign user to other groups for (Group newGroup : newUser.getGroups()) { Group group = groupDAO.findById(newGroup.getId()); group.addUser(user); } auditLogger.log(now, ServerSessionUtil.getUsername(), ServerSessionUtil.getIP(), "add user", userTarget(user), true, ""); } @Override @Transactional(propagation=Propagation.REQUIRED) public void updateUser(User updateUser) { Date now = new Date(); String action = "update user"; User loggedInUser = getLoggedInUser(); if (authorizer.isAuthorized(loggedInUser, Function.UPDATE_USER.name())) { // update base user User user = userDAO.findById(updateUser.getId()); user.setFullname(updateUser.getFullname()); user.setEmail(updateUser.getEmail()); user.setActiveFlag(updateUser.isActiveFlag()); if (!updateUser.getAuthnPasswordValue().equals("")) { user.updateAuthnPasswordValue(digester.digest(updateUser.getAuthnPasswordValue())); } // remove old groups for (Group oldGroup : user.getGroups()) { Group group = groupDAO.findById(oldGroup.getId()); group.removeUser(user); } // assign everyone group Group everyoneGroup = getEveryoneGroup(); everyoneGroup.addUser(user); // add new groups for (Group newGroup : updateUser.getGroups()) { Group group = groupDAO.findById(newGroup.getId()); group.addUser(user); } auditLogger.log(now, ServerSessionUtil.getUsername(), ServerSessionUtil.getIP(), action, userTarget(updateUser), true, ""); } else { auditLogger.log(now, ServerSessionUtil.getUsername(), ServerSessionUtil.getIP(), action, userTarget(updateUser), false, "not authorized"); throw new RuntimeException("Not Authorized!"); } } @Override @Transactional(propagation=Propagation.REQUIRED, readOnly=true) public List<User> getUsers(boolean includeOnlyActive) { List<User> users = userDAO.findAllUsers(includeOnlyActive); LOG.debug("found "+users.size()+" users"); return users; } @Override @Transactional(propagation=Propagation.REQUIRED) public void verifyInitialization() { verifyEveryoneGroupExists(); verifyAdminUserExists(); } @Transactional(propagation=Propagation.REQUIRED) private void verifyAdminUserExists() { User adminUser = getAdminUser(); if (null == adminUser) { adminUser = User.newActiveUser(ADMIN_USER_NAME, ADMIN_USER_NAME, ADMIN_USER_NAME, ADMIN_USER_NAME+"@"+ADMIN_USER_NAME+".com"); adminUser.addGroup(getEveryoneGroup()); addUserInternal(adminUser); } } @Transactional(propagation=Propagation.REQUIRED) private void verifyEveryoneGroupExists() { Group everyoneGroup = getEveryoneGroup(); if (null == everyoneGroup) { everyoneGroup = new Group(EVERYONE_GROUP_NAME); addGroupInternal(everyoneGroup); } } @Override @Transactional(propagation=Propagation.REQUIRED) public void addGroup(Group group) { Date now = new Date(); User loggedInUser = getLoggedInUser(); if (authorizer.isAuthorized(loggedInUser, Function.ADD_GROUP.name())) { addGroupInternal(group); } else { auditLogger.log(now, ServerSessionUtil.getUsername(), ServerSessionUtil.getIP(), "add group", groupTarget(group), false, "not authorized"); throw new RuntimeException("Not Authorized!"); } } @Transactional(propagation=Propagation.REQUIRED) private void addGroupInternal(Group group) { Date now = new Date(); // update users Set<User> users = new HashSet<User>(group.getUsers()); group.removeUsers(); for (User user : users) { User pUser = userDAO.findById(user.getId()); group.addUser(pUser); } groupDAO.makePersistent(group); auditLogger.log(now, ServerSessionUtil.getUsername(), ServerSessionUtil.getIP(), "add group", groupTarget(group), true, ""); } @Override @Transactional(propagation=Propagation.REQUIRED) public void updateGroup(Group updateGroup) { Date now = new Date(); String action = "update group"; User loggedInUser = getLoggedInUser(); if (authorizer.isAuthorized(loggedInUser, Function.UPDATE_GROUP.name())) { Group group = groupDAO.findById(updateGroup.getId()); String groupMessage = (updateGroup.getName().equals(group.getName())) ? "" : ("was: "+groupTarget(group)); group.setName(updateGroup.getName()); group.removeUsers(); for (User user : updateGroup.getUsers()) { User pUser = userDAO.findById(user.getId()); group.addUser(pUser); } auditLogger.log(now, loggedInUser.getUsername(), ServerSessionUtil.getIP(), action, groupTarget(updateGroup), true, groupMessage); } else { auditLogger.log(now, ServerSessionUtil.getUsername(), ServerSessionUtil.getIP(), action, groupTarget(updateGroup), false, "not authorized"); throw new RuntimeException("Not Authorized!"); } } @Override @Transactional(propagation=Propagation.REQUIRED) public void deleteGroup(Group updateGroup) { Date now = new Date(); String action = "delete group"; User loggedInUser = getLoggedInUser(); if (authorizer.isAuthorized(loggedInUser, Function.DELETE_GROUP.name())) { Group group = groupDAO.findById(updateGroup.getId()); if (group != null) { // remove associated password permissions List<Password> passwords = passwordDAO.findPasswordsByPermissionSubject(group); for (Password password : passwords) { password.removePermissionsBySubject(group); passwordDAO.makePersistent(password); } // remove associated template details List<Template> templates = templateDAO.findTemplatesByDetailSubject(group); for (Template template : templates) { template.removeDetailsBySubject(group); templateDAO.makePersistent(template); } // remove associated users group.removeUsers(); // actually remove group groupDAO.flush(); groupDAO.makeTransient(group); auditLogger.log(now, loggedInUser.getUsername(), ServerSessionUtil.getIP(), action, groupTarget(group), true, ""); } else { auditLogger.log(now, ServerSessionUtil.getUsername(), ServerSessionUtil.getIP(), action, groupTarget(updateGroup), false, "invalid id"); } } else { auditLogger.log(now, ServerSessionUtil.getUsername(), ServerSessionUtil.getIP(), action, groupTarget(updateGroup), false, "not authorized"); throw new RuntimeException("Not Authorized!"); } } @Override @Transactional(propagation=Propagation.REQUIRED, readOnly=true) public List<Group> getGroups(boolean includeEveryoneGroup) { List<Group> groups = groupDAO.findAll(); if (!includeEveryoneGroup) { groups.remove(new Group(EVERYONE_GROUP_NAME)); } LOG.debug("found "+groups.size()+" groups"); return groups; } @Override @Transactional(propagation=Propagation.REQUIRED, readOnly=true) public List<Subject> getSubjects(boolean includeOnlyActive) { List<User> users = getUsers(includeOnlyActive); List<Group> groups = getGroups(true); List<Subject> subjects = new ArrayList<Subject>(users.size()+groups.size()); subjects.addAll(users); subjects.addAll(groups); LOG.debug("found "+subjects.size()+" subjects"); return subjects; } @Override @Transactional(propagation=Propagation.REQUIRED, readOnly=true) public Group getEveryoneGroup() { return groupDAO.findGroupByName(EVERYONE_GROUP_NAME); } @Transactional(propagation=Propagation.REQUIRED, readOnly=true) private User getAdminUser() { return userDAO.findUserByUsername(ADMIN_USER_NAME); } @Override @Transactional(propagation=Propagation.REQUIRED, readOnly=true) public Group getGroupWithUsers(long groupId) { Group group = groupDAO.findById(groupId); // fetch users int numUsers = group.getUsers().size(); LOG.debug(group.getName()+" has "+numUsers+" users"); return group; } @Override @Transactional(propagation=Propagation.REQUIRED, readOnly=true) public User getUserWithGroups(long userId) { User user = userDAO.findById(userId); // fetch groups int numGroups = user.getGroups().size(); LOG.debug(user.getName()+" has "+numGroups+" groups"); return user; } @Override @Transactional(propagation=Propagation.REQUIRED, readOnly=true) public UserAuthnTOTP getCurrentUserTOTP() { User loggedInUser = getLoggedInUser(); UserAuthnTOTP userAuthnTOTP = new UserAuthnTOTP(); userAuthnTOTP.setEnabled(loggedInUser.getAuthnTOTPValue().isEnabled()); userAuthnTOTP.setKey(loggedInUser.getAuthnTOTPValue().getKey().equals("") ? "" : encryptor.decrypt(loggedInUser.getAuthnTOTPValue().getKey())); return userAuthnTOTP; } @Override @Transactional(propagation=Propagation.REQUIRED) public void updateCurrentUserTOTP(UserAuthnTOTP userAuthnTOTP) { User loggedInUser = getLoggedInUser(); loggedInUser.updateAuthnTOTP(userAuthnTOTP.isEnabled(), encryptor.encrypt(userAuthnTOTP.getKey())); } @Override public String generateTOTPKey() { return TwoStepTOTPAuthenticator.generateKey(); } @Override @Transactional(propagation=Propagation.REQUIRED, readOnly=true) public boolean isUserTaken(String username) { User user = userDAO.findUserByUsername(username); return (null != user); } @Override @Transactional(propagation=Propagation.REQUIRED, readOnly=true) public boolean isGroupTaken(String groupName, long ignoreGroupId) { boolean isGroupTaken = false; Group group = groupDAO.findGroupByName(groupName); if (group != null) { if (group.getId() != ignoreGroupId) { isGroupTaken = true; } } return isGroupTaken; } @Override @Transactional(propagation=Propagation.REQUIRED) public boolean unblockIP(String ipaddress) { boolean isUnblocked = false; Date now = new Date(); String action = "unblock ip"; User loggedInUser = getLoggedInUser(); if (authorizer.isAuthorized(loggedInUser, Function.UNBLOCK_IP.name())) { IPLockout ipLockout = ipLockoutDAO.findByIP(ipaddress); if ((null != ipLockout) && (null != ipLockout.getLockoutDate())) { ipLockout.setLockoutDate(null); ipLockout.setFailCount(0); isUnblocked = true; auditLogger.log(now, ServerSessionUtil.getUsername(), ServerSessionUtil.getIP(), action, ipaddress, true, ""); } else { auditLogger.log(now, ServerSessionUtil.getUsername(), ServerSessionUtil.getIP(), action, ipaddress, false, "doesn't exist"); } } else { auditLogger.log(now, ServerSessionUtil.getUsername(), ServerSessionUtil.getIP(), action, ipaddress, false, "not authorized"); throw new RuntimeException("Not Authorized!"); } return isUnblocked; } private User getLoggedInUser() { User loggedInUser = loginService.getLogin(); if (null == loggedInUser) { throw new RuntimeException("Not Logged In!"); } return loggedInUser; } private String userTarget(User user) { return user.getUsername() + " (userId="+user.getId()+")"; } private String groupTarget(Group group) { return group.getName() + " (groupId="+group.getId()+")"; } }