/* Copyright 2008-2015 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.Collection; import java.util.Collections; 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.PasswordService; import net.webpasswordsafe.common.model.AccessLevel; import net.webpasswordsafe.common.model.Password; import net.webpasswordsafe.common.model.PasswordAccessAudit; import net.webpasswordsafe.common.model.PasswordData; import net.webpasswordsafe.common.model.Permission; import net.webpasswordsafe.common.model.Tag; import net.webpasswordsafe.common.model.Template; import net.webpasswordsafe.common.model.TemplateDetail; import net.webpasswordsafe.common.model.User; import net.webpasswordsafe.common.util.Constants.Match; import net.webpasswordsafe.common.util.Utils; import net.webpasswordsafe.common.util.Constants.Function; import net.webpasswordsafe.server.ServerSessionUtil; import net.webpasswordsafe.server.dao.PasswordAccessAuditDAO; import net.webpasswordsafe.server.dao.PasswordDAO; import net.webpasswordsafe.server.dao.TagDAO; import net.webpasswordsafe.server.dao.TemplateDAO; import net.webpasswordsafe.server.plugin.audit.AuditLogger; import net.webpasswordsafe.server.plugin.authorization.Authorizer; import net.webpasswordsafe.server.plugin.encryption.Encryptor; import net.webpasswordsafe.server.plugin.generator.PasswordGenerator; 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; /** * Implementation of Password Service * * @author Josh Drummond * */ @Service("passwordService") public class PasswordServiceImpl extends WPSXsrfProtectedServiceServlet implements PasswordService { private static final long serialVersionUID = -2328314230839034189L; private static Logger LOG = Logger.getLogger(PasswordServiceImpl.class); @Autowired private PasswordDAO passwordDAO; @Autowired private TagDAO tagDAO; @Autowired private PasswordAccessAuditDAO passwordAccessAuditDAO; @Autowired private TemplateDAO templateDAO; @Resource private PasswordGenerator passwordGenerator; @Autowired private LoginService loginService; @Resource private Encryptor encryptor; @Resource private AuditLogger auditLogger; @Resource private Authorizer authorizer; @Override @Transactional(propagation=Propagation.REQUIRED) public void addPassword(Password password) { Date now = new Date(); String action = "add password"; User loggedInUser = getLoggedInUser(); if (authorizer.isAuthorized(loggedInUser, Function.ADD_PASSWORD.name())) { if (password.getPermissions().size() > 0) { password.setUserCreated(loggedInUser); password.setDateCreated(now); password.setUserLastUpdate(loggedInUser); password.setDateLastUpdate(now); password.getCurrentPasswordData().setUserCreated(loggedInUser); password.getCurrentPasswordData().setDateCreated(now); password.getCurrentPasswordData().setPassword(encryptor.encrypt(password.getCurrentPasswordData().getPassword())); // update tags Set<Tag> tags = new HashSet<Tag>(password.getTags()); password.removeTags(); for (Tag tag : tags) { Tag pTag = tagDAO.findTagByName(tag.getName()); if (null != pTag) { password.addTag(pTag); } else { password.addTag(tag); } } passwordDAO.makePersistent(password); auditLogger.log(now, loggedInUser.getUsername(), ServerSessionUtil.getIP(), action, passwordTarget(password), true, ""); } else { auditLogger.log(now, ServerSessionUtil.getUsername(), ServerSessionUtil.getIP(), action, passwordTarget(password), false, "missing permissions"); throw new RuntimeException("Missing Permissions"); } } else { auditLogger.log(now, ServerSessionUtil.getUsername(), ServerSessionUtil.getIP(), action, passwordTarget(password), false, "not authorized"); throw new RuntimeException("Not Authorized!"); } } @Override @Transactional(propagation=Propagation.REQUIRED) public void updatePassword(Password updatePassword) { LOG.debug("updating password"); Date now = new Date(); String action = "update password"; User loggedInUser = getLoggedInUser(); Password password = passwordDAO.findAllowedPasswordById(updatePassword.getId(), loggedInUser, AccessLevel.WRITE); if (password != null) { if (updatePassword.getPermissions().size() > 0) { String passwordMessage = (updatePassword.getName().equals(password.getName())) ? "" : ("was: "+passwordTarget(password)); // update simple fields password.setName(updatePassword.getName()); password.setUsername(updatePassword.getUsername()); password.setNotes(updatePassword.getNotes()); password.setDateLastUpdate(now); password.setUserLastUpdate(loggedInUser); password.setActive(updatePassword.isActive()); password.setMaxHistory(updatePassword.getMaxHistory()); // update tags password.removeTags(); for (Tag tag : updatePassword.getTags()) { Tag pTag = tagDAO.findTagByName(tag.getName()); if (null != pTag) { password.addTag(pTag); } else { password.addTag(tag); } } // update password data, push others back in history if applicable PasswordData updatePasswordData = updatePassword.getCurrentPasswordData(); String updatePasswordVal = updatePasswordData.getPassword(); // if user entered a password value and its not the same as the current one... if (!"".equals(updatePasswordVal)) { String currentPasswordVal = encryptor.decrypt(password.getCurrentPasswordData().getPassword()); if (!updatePasswordVal.equals(currentPasswordVal)) { updatePasswordData.setUserCreated(loggedInUser); updatePasswordData.setDateCreated(now); updatePasswordData.setPassword(encryptor.encrypt(updatePasswordVal)); password.addPasswordData(updatePasswordData); } } // trim history if not infinite password.pruneDataHistory(); // update permissions if allowed to grant if (passwordDAO.findAllowedPasswordById(updatePassword.getId(), loggedInUser, AccessLevel.GRANT) != null) { // keep the permissions that haven't changed password.getPermissions().retainAll(updatePassword.getPermissions()); // add the permissions that have changed for (Permission permission : updatePassword.getPermissions()) { if (permission.getId() == 0) { password.addPermission(permission); } } } else { LOG.debug("no access to grant permissions"); } auditLogger.log(now, loggedInUser.getUsername(), ServerSessionUtil.getIP(), action, passwordTarget(updatePassword), true, passwordMessage); } else { auditLogger.log(now, loggedInUser.getUsername(), ServerSessionUtil.getIP(), action, passwordTarget(updatePassword), false, "missing permissions"); throw new RuntimeException("Missing Permissions"); } } else { auditLogger.log(now, loggedInUser.getUsername(), ServerSessionUtil.getIP(), action, passwordTarget(updatePassword), false, "write access denied"); } } @Override @Transactional(propagation=Propagation.REQUIRED, readOnly=true) public List<Password> searchPassword(String query, boolean activeOnly, Collection<Tag> tags, Match tagMatch) { query = Utils.safeString(query); Date now = new Date(); User loggedInUser = getLoggedInUser(); List<Password> passwords = new ArrayList<Password>(); Set<String> searchTerms = parseSearchTerms(query); if (searchTerms.isEmpty()) { passwords.addAll(passwordDAO.findPasswordByFuzzySearch("", loggedInUser, activeOnly, tags, tagMatch)); } else { Set<Password> passwordSet = new HashSet<Password>(); for (String searchTerm : searchTerms) { passwordSet.addAll(passwordDAO.findPasswordByFuzzySearch(searchTerm, loggedInUser, activeOnly, tags, tagMatch)); } passwords.addAll(passwordSet); } Collections.sort(passwords); auditLogger.log(now, loggedInUser.getUsername(), ServerSessionUtil.getIP(), "search password", "query=["+query+"] activeOnly=["+activeOnly+"] tags=["+tags+"] tagMatch=["+tagMatch+"]", true, "found "+passwords.size()); return passwords; } private Set<String> parseSearchTerms(String query) { Set<String> searchTerms = new HashSet<String>(); String[] sOuter = query.split("\""); boolean insideQuote = false; for (String sOuterItem : sOuter) { if (insideQuote) { searchTerms.add(sOuterItem.trim()); } else { String[] sInner = sOuterItem.split(" "); for (String sInnerItem : sInner) { searchTerms.add(sInnerItem.trim()); } } insideQuote = !insideQuote; } searchTerms.remove(""); return searchTerms; } @Override @Transactional(propagation=Propagation.REQUIRED, readOnly=true) public String generatePassword() { LOG.debug("generating password..."); return passwordGenerator.generatePassword(); } @Override @Transactional(propagation=Propagation.REQUIRED) public String getCurrentPassword(long passwordId) { String currentPasswordValue = ""; Date now = new Date(); String action = "get current password value"; User loggedInUser = getLoggedInUser(); Password password = passwordDAO.findAllowedPasswordById(passwordId, loggedInUser, AccessLevel.READ); if (password != null) { auditLogger.log(now, loggedInUser.getUsername(), ServerSessionUtil.getIP(), action, passwordTarget(password), true, ""); currentPasswordValue = encryptor.decrypt(password.getCurrentPasswordData().getPassword()); createPasswordAccessAuditEntry(password, loggedInUser); } else { auditLogger.log(now, loggedInUser.getUsername(), ServerSessionUtil.getIP(), action, passwordTarget(passwordId), false, "invalid id or no access"); } return currentPasswordValue; } @Transactional(propagation=Propagation.REQUIRED) private void createPasswordAccessAuditEntry(Password password, User user) { LOG.debug("creating access audit entry for password=["+password.getName()+"] user=["+user.getName()+"]"); PasswordAccessAudit passwordAccessAudit = new PasswordAccessAudit(); passwordAccessAudit.setDateAccessed(new Date()); passwordAccessAudit.setPassword(password); passwordAccessAudit.setUser(user); passwordAccessAuditDAO.makePersistent(passwordAccessAudit); } @Override @Transactional(propagation=Propagation.REQUIRED, readOnly=true) public Password getPassword(long passwordId) { Date now = new Date(); String action = "get password"; User loggedInUser = getLoggedInUser(); Password password = passwordDAO.findAllowedPasswordById(passwordId, loggedInUser, AccessLevel.READ); if (password != null) { password.setMaxEffectiveAccessLevel(passwordDAO.getMaxEffectiveAccessLevel(password, loggedInUser)); auditLogger.log(now, loggedInUser.getUsername(), ServerSessionUtil.getIP(), action, passwordTarget(password), true, ""); } else { auditLogger.log(now, loggedInUser.getUsername(), ServerSessionUtil.getIP(), action, passwordTarget(passwordId), false, "invalid id or no access"); } return password; } @Override @Transactional(propagation=Propagation.REQUIRED, readOnly=true) public List<PasswordAccessAudit> getPasswordAccessAuditData(long passwordId) { Date now = new Date(); String action = "get password access audit data"; List<PasswordAccessAudit> accessAuditList = new ArrayList<PasswordAccessAudit>(0); User loggedInUser = getLoggedInUser(); Password password = passwordDAO.findAllowedPasswordById(passwordId, loggedInUser, AccessLevel.READ); if (null != password) { accessAuditList = passwordAccessAuditDAO.findAccessAuditByPassword(password); auditLogger.log(now, loggedInUser.getUsername(), ServerSessionUtil.getIP(), action, passwordTarget(password), true, ""); } else { auditLogger.log(now, loggedInUser.getUsername(), ServerSessionUtil.getIP(), action, passwordTarget(passwordId), false, "invalid id or no access"); } LOG.debug("found "+accessAuditList.size() + " password access audit entries"); return accessAuditList; } @Override @Transactional(propagation=Propagation.REQUIRED) public List<PasswordData> getPasswordHistoryData(long passwordId) { Date now = new Date(); String action = "get password history data"; List<PasswordData> decryptedPasswordDataList = new ArrayList<PasswordData>(0); User loggedInUser = getLoggedInUser(); Password password = passwordDAO.findAllowedPasswordById(passwordId, loggedInUser, AccessLevel.READ); if (null != password) { decryptedPasswordDataList = new ArrayList<PasswordData>(password.getPasswordData().size()); for (PasswordData passwordData : password.getPasswordData()) { decryptedPasswordDataList.add(new PasswordData(encryptor.decrypt(passwordData.getPassword()), passwordData.getDateCreated(), passwordData.getUserCreated())); } createPasswordAccessAuditEntry(password, loggedInUser); auditLogger.log(now, loggedInUser.getUsername(), ServerSessionUtil.getIP(), action, passwordTarget(password), true, ""); } else { auditLogger.log(now, loggedInUser.getUsername(), ServerSessionUtil.getIP(), action, passwordTarget(passwordId), false, "invalid id or no access"); } LOG.debug("found "+decryptedPasswordDataList.size() + " password history values"); return decryptedPasswordDataList; } @Override @Transactional(propagation=Propagation.REQUIRED, readOnly=true) public List<Tag> getAllTags() { List<Tag> tags = tagDAO.findTagsInUse(); LOG.debug("found "+tags.size() + " tags"); return tags; } @Override @Transactional(propagation=Propagation.REQUIRED, readOnly=true) public List<Tag> getAvailableTags() { User loggedInUser = getLoggedInUser(); List<Tag> tags = null; if (authorizer.isAuthorized(loggedInUser, Function.BYPASS_PASSWORD_PERMISSIONS.name())) { tags = getAllTags(); } else { tags = tagDAO.findTagsByUser(loggedInUser); } LOG.debug("found "+tags.size() + " user tags"); return tags; } @Override @Transactional(propagation=Propagation.REQUIRED) public void addTemplate(Template template) { Date now = new Date(); String action = "add template"; User loggedInUser = getLoggedInUser(); template.setUser(loggedInUser); templateDAO.makePersistent(template); auditLogger.log(now, loggedInUser.getUsername(), ServerSessionUtil.getIP(), action, templateTarget(template), true, ""); } @Override @Transactional(propagation=Propagation.REQUIRED) public void updateTemplate(Template updateTemplate) { LOG.debug("updating template"); Date now = new Date(); String action = "update template"; User loggedInUser = getLoggedInUser(); Template template = templateDAO.findUpdatableTemplateById(updateTemplate.getId(), loggedInUser); if (template != null) { String templateMessage = (updateTemplate.getName().equals(template.getName())) ? "" : ("was: "+templateTarget(template)); // update simple fields template.setName(updateTemplate.getName()); // only change sharing status if original owner is updating or special bypass authz if ((template.getUser().getId() == loggedInUser.getId()) || authorizer.isAuthorized(loggedInUser, Function.BYPASS_TEMPLATE_SHARING.name())) { template.setShared(updateTemplate.isShared()); } // update details // keep the permissions that haven't changed template.getTemplateDetails().retainAll(updateTemplate.getTemplateDetails()); // add the permissions that have changed for (TemplateDetail templateDetail : updateTemplate.getTemplateDetails()) { if (templateDetail.getId() == 0) { template.addDetail(templateDetail); } } auditLogger.log(now, loggedInUser.getUsername(), ServerSessionUtil.getIP(), action, templateTarget(updateTemplate), true, templateMessage); } else { auditLogger.log(now, loggedInUser.getUsername(), ServerSessionUtil.getIP(), action, templateTarget(updateTemplate), false, "invalid id or no access"); } } @Override @Transactional(propagation=Propagation.REQUIRED) public void deleteTemplate(Template updateTemplate) { LOG.debug("deleting template"); Date now = new Date(); String action = "delete template"; User loggedInUser = getLoggedInUser(); Template template = templateDAO.findUpdatableTemplateById(updateTemplate.getId(), loggedInUser); if (template != null) { // only allow delete if original owner or special bypass authz if ((template.getUser().getId() == loggedInUser.getId()) || authorizer.isAuthorized(loggedInUser, Function.BYPASS_TEMPLATE_SHARING.name())) { templateDAO.makeTransient(template); auditLogger.log(now, loggedInUser.getUsername(), ServerSessionUtil.getIP(), action, templateTarget(template), true, ""); } else { auditLogger.log(now, loggedInUser.getUsername(), ServerSessionUtil.getIP(), action, templateTarget(template), false, "no access"); } } else { auditLogger.log(now, loggedInUser.getUsername(), ServerSessionUtil.getIP(), action, templateTarget(updateTemplate), false, "invalid id or no access"); } } @Override @Transactional(propagation=Propagation.REQUIRED, readOnly=true) public List<Template> getTemplates(boolean includeShared) { User loggedInUser = getLoggedInUser(); return templateDAO.findTemplatesByUser(loggedInUser, includeShared); } @Override @Transactional(propagation=Propagation.REQUIRED, readOnly=true) public Template getTemplateWithDetails(long templateId) { Template template = templateDAO.findById(templateId); if (template != null) { template.getTemplateDetails().size(); } return template; } @Override @Transactional(propagation=Propagation.REQUIRED, readOnly=true) public boolean isPasswordTaken(String passwordName, String username, long ignorePasswordId) { boolean isPasswordTaken = false; Password password = passwordDAO.findPasswordByName(passwordName, username); if (password != null) { if (password.getId() != ignorePasswordId) { isPasswordTaken = true; } } return isPasswordTaken; } @Override @Transactional(propagation=Propagation.REQUIRED, readOnly=true) public boolean isTemplateTaken(String templateName, long ignoreTemplateId) { boolean isTemplateTaken = false; Template template = templateDAO.findTemplateByName(templateName); if (template != null) { if (template.getId() != ignoreTemplateId) { isTemplateTaken = true; } } return isTemplateTaken; } private User getLoggedInUser() { User loggedInUser = loginService.getLogin(); if (null == loggedInUser) { throw new RuntimeException("Not Logged In!"); } return loggedInUser; } private String passwordTarget(Password password) { return password.getName() + " (passwordId="+password.getId()+")"; } private String passwordTarget(long passwordId) { return "passwordId="+passwordId; } private String templateTarget(Template template) { return template.getName() + " (templateId="+template.getId()+")"; } }