From 4ec319fc4524851fa14e399fe9514d614f263e29 Mon Sep 17 00:00:00 2001 From: Joern Muehlencord Date: Fri, 17 May 2019 16:35:34 +0200 Subject: [PATCH] fixed reset password process --- .../account/control/AccountControl.java | 128 ++++++++++-------- .../business/mail/boundary/MailService.java | 44 +++--- 2 files changed, 99 insertions(+), 73 deletions(-) diff --git a/account/src/main/java/de/muehlencord/shared/account/business/account/control/AccountControl.java b/account/src/main/java/de/muehlencord/shared/account/business/account/control/AccountControl.java index c891223..335a330 100644 --- a/account/src/main/java/de/muehlencord/shared/account/business/account/control/AccountControl.java +++ b/account/src/main/java/de/muehlencord/shared/account/business/account/control/AccountControl.java @@ -21,6 +21,8 @@ import de.muehlencord.shared.account.business.account.entity.AccountLoginEntity; import de.muehlencord.shared.account.business.account.entity.AccountStatus; import de.muehlencord.shared.account.business.application.entity.ApplicationEntity; import de.muehlencord.shared.account.business.application.entity.ApplicationRoleEntity; +import de.muehlencord.shared.account.business.config.boundary.ConfigService; +import de.muehlencord.shared.account.business.config.entity.ConfigException; import de.muehlencord.shared.account.business.instance.boundary.ApplicationPermissions; import de.muehlencord.shared.account.business.mail.boundary.MailService; import de.muehlencord.shared.account.business.mail.entity.MailException; @@ -65,10 +67,13 @@ public class AccountControl implements Serializable { @Inject private ApplicationEntity application; + @Inject + ConfigService configService; + @Inject @AccountPU EntityManager em; - + public List getAllAccounts(boolean includeDisabled) { List resultList; if (includeDisabled) { @@ -76,25 +81,25 @@ public class AccountControl implements Serializable { } else { resultList = getActiveAccounts(); } - + if (SecurityUtil.checkPermission(ApplicationPermissions.ACCOUNT_LIST)) { return resultList; } else { String currentUserName = SecurityUtils.getSubject().getPrincipal().toString(); return resultList.stream() .filter(account -> account.getAccountLogin() != null) - .filter (account -> account.getUsername().equals (currentUserName)) + .filter(account -> account.getUsername().equals(currentUserName)) .collect(Collectors.toList()); } - } - + } + /** * returns a list of active accounts * * @return a list of active accounts */ private List getActiveAccounts() { - Query query = em.createNamedQuery ("AccountEntity.findActiveAccounts"); + Query query = em.createNamedQuery("AccountEntity.findActiveAccounts"); query.setParameter("status", AccountStatus.DISABLED.name()); return query.getResultList(); } @@ -109,7 +114,6 @@ public class AccountControl implements Serializable { return query.getResultList(); } - @Lock(LockType.READ) @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) public AccountEntity getAccountEntity(String userName, boolean loadRoles) { @@ -121,7 +125,6 @@ public class AccountControl implements Serializable { queryBuilder.append("WHERE a.username = :username"); Query query = em.createQuery(queryBuilder.toString()); query.setParameter("username", userName); - query.setHint("org.hibernate.cacheable", true); try { return (AccountEntity) query.getSingleResult(); } catch (NoResultException ex) { @@ -207,6 +210,7 @@ public class AccountControl implements Serializable { } + @Transactional public boolean initPasswordReset(String userName) { try { AccountEntity account = getAccountEntity(userName, false); @@ -215,6 +219,11 @@ public class AccountControl implements Serializable { return false; } + if (account.getAccountLogin() == null) { + LOGGER.error("No login for account {} defined, cannot reset password", userName); + return false; + } + if (account.getStatus().equals(AccountStatus.BLOCKED.name())) { LOGGER.warn("Account " + userName + " is locked, cannot initialize password reset"); return false; @@ -225,15 +234,15 @@ public class AccountControl implements Serializable { Date validTo = DateUtil.getCurrentTimeInUTC(); validTo = new Date(validTo.getTime() + 1000 * 600); // 10 minutes to react - // TODO rework password reset -// account.setPasswordResetHash(randomString); -// account.setPasswordResetOngoing(true); -// account.setPasswordResetValidTo(validTo); + account.getAccountLogin().setPasswordResetHash(randomString); + account.getAccountLogin().setPasswordResetOngoing(true); + account.getAccountLogin().setPasswordResetValidTo(validTo); mailService.sendPasswortResetStartEmail(account, randomString); + em.merge(account.getAccountLogin()); em.merge(account); return true; - } catch (MailException ex) { + } catch (MailException | ConfigException ex) { LOGGER.error("Error while sending password reset mail. " + ex.toString()); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Error while sending password reset mail.", ex); @@ -242,6 +251,7 @@ public class AccountControl implements Serializable { } } + @Transactional public boolean resetPassword(String userName, String newPassword, String resetPasswordToken) { AccountEntity account = getAccountEntity(userName, false); @@ -251,11 +261,15 @@ public class AccountControl implements Serializable { return false; } - /* - if (account.getPasswordResetOngoing() && (account.getPasswordResetHash() != null) && (account.getPasswordResetValidTo() != null)) { + if (account.getAccountLogin() == null) { + // user has no defined login, cannot reset password + return false; + } + + if (account.getAccountLogin().getPasswordResetOngoing() && (account.getAccountLogin().getPasswordResetHash() != null) && (account.getAccountLogin().getPasswordResetValidTo() != null)) { Date now = DateUtil.getCurrentTimeInUTC(); - String storedHash = account.getPasswordResetHash().trim(); - if (account.getPasswordResetValidTo().after(now)) { + String storedHash = account.getAccountLogin().getPasswordResetHash().trim(); + if (account.getAccountLogin().getPasswordResetValidTo().after(now)) { if (storedHash.equals(resetPasswordToken)) { // everything ok, reset password executePasswordReset(account, newPassword); @@ -279,26 +293,26 @@ public class AccountControl implements Serializable { addLoginError(account); return false; } - */ - return false; // FIMXE re-implement password reset } private void executePasswordReset(AccountEntity account, String newPassword) { Date now = DateUtil.getCurrentTimeInUTC(); String hashedPassword = SecurityUtil.createPassword(newPassword); -// account.setAccountPassword(hashedPassword); -// -// account.setPasswordResetOngoing(false); -// account.setPasswordResetHash(null); -// account.setPasswordResetValidTo(null); - + if (account.getAccountLogin() == null) { + return; + } + account.getAccountLogin().setAccountPassword(hashedPassword); + account.getAccountLogin().setPasswordResetOngoing(false); + account.getAccountLogin().setPasswordResetHash(null); + account.getAccountLogin().setPasswordResetValidTo(null); account.setLastUpdatedBy(account.getUsername()); account.setLastUpdatedOn(now); em.merge(account); } + @Transactional public AccountLoginEntity updateSuccessFullLogin(AccountLoginEntity login, String byUser) { Date now = DateUtil.getCurrentTimeInUTC(); // a scucessful login ends a password reset procedure @@ -315,10 +329,12 @@ public class AccountControl implements Serializable { return updateLogin(login); } + @Transactional public AccountLoginEntity updateLogin(AccountLoginEntity login) { return em.merge(login); } + @Transactional public void updateLogin(AccountEntity account) { if (account.getAccountLogin() == null) { // TODO connect to IPRS - how can an account ask for an updated login if the user cannot login @@ -327,35 +343,39 @@ public class AccountControl implements Serializable { } } + @Transactional public void addLoginError(AccountEntity account) { - // TODO reimplement -// try { -// Date now = DateUtil.getCurrentTimeInUTC(); -// account.setLastFailedLogin(now); -// account.setFailureCount(account.getFailureCount() + 1); -// -// int maxFailedLogins = Integer.parseInt(configService.getConfigValue( "account.maxFailedLogins")); -// if ((account.getFailureCount() >= maxFailedLogins) && (!account.getStatus().equals("LOCKED"))) { // TOD add status enum -// // max failed logins reached, disabling user -// LOGGER.info("Locking account " + account.getUsername() + " due to " + account.getFailureCount() + " failed logins"); -// account.setStatus(AccountStatus.BLOCKED.name()); -// } -// -// // on a failed login request, disable password reset -// account.setPasswordResetOngoing(false); -// account.setPasswordResetHash(null); -// account.setPasswordResetValidTo(null); -// -// account.setLastUpdatedBy("system"); -// account.setLastUpdatedOn(now); -// em.merge(account); -// } catch (ConfigException ex) { -// if (LOGGER.isDebugEnabled()) { -// LOGGER.debug(ex.toString(), ex); -// } else { -// LOGGER.error(ex.toString()); -// } -// } + if (account.getAccountLogin() == null) { + LOGGER.error("No login defined for {}", account.getUsername()); + } else { + try { + Date now = DateUtil.getCurrentTimeInUTC(); + account.getAccountLogin().setLastFailedLogin(now); + account.getAccountLogin().setFailureCount(account.getAccountLogin().getFailureCount() + 1); + + int maxFailedLogins = Integer.parseInt(configService.getConfigValue("account.maxFailedLogins")); + if ((account.getAccountLogin().getFailureCount() >= maxFailedLogins) && (!account.getStatus().equals("LOCKED"))) { // TOD add status enum + // max failed logins reached, disabling user + LOGGER.info("Locking account " + account.getUsername() + " due to " + account.getAccountLogin().getFailureCount() + " failed logins"); + account.setStatus(AccountStatus.BLOCKED.name()); + } + + // on a failed login request, disable password reset + account.getAccountLogin().setPasswordResetOngoing(false); + account.getAccountLogin().setPasswordResetHash(null); + account.getAccountLogin().setPasswordResetValidTo(null); + + account.setLastUpdatedBy("system"); + account.setLastUpdatedOn(now); + em.merge(account); + } catch (ConfigException ex) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(ex.toString(), ex); + } else { + LOGGER.error(ex.toString()); + } + } + } } public AccountLoginEntity createLoginWithRandomPassword() { @@ -403,5 +423,5 @@ public class AccountControl implements Serializable { em.remove(login); em.merge(account); } - + } diff --git a/account/src/main/java/de/muehlencord/shared/account/business/mail/boundary/MailService.java b/account/src/main/java/de/muehlencord/shared/account/business/mail/boundary/MailService.java index ae78666..7ceaddd 100644 --- a/account/src/main/java/de/muehlencord/shared/account/business/mail/boundary/MailService.java +++ b/account/src/main/java/de/muehlencord/shared/account/business/mail/boundary/MailService.java @@ -15,16 +15,22 @@ */ package de.muehlencord.shared.account.business.mail.boundary; -import de.muehlencord.shared.account.business.mail.entity.MailTemplateException; -import de.muehlencord.shared.account.business.account.entity.AccountConfigurationKey; import de.muehlencord.shared.account.business.account.entity.AccountEntity; import de.muehlencord.shared.account.business.account.entity.AccountLoginEntity; +import de.muehlencord.shared.account.business.config.boundary.ConfigService; +import de.muehlencord.shared.account.business.config.entity.ConfigException; import de.muehlencord.shared.account.business.mail.entity.MailDatamodel; import de.muehlencord.shared.account.business.mail.entity.MailException; +import de.muehlencord.shared.account.business.mail.entity.MailTemplateException; +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.nio.file.Files; +import java.util.ArrayList; import java.util.Date; +import java.util.List; import java.util.UUID; import javax.annotation.Resource; -import javax.ejb.EJB; import javax.ejb.Stateless; import javax.inject.Inject; import javax.mail.Message; @@ -38,13 +44,6 @@ import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import de.muehlencord.shared.account.business.account.entity.AccountConfigurationValue; -import java.io.File; -import java.io.IOException; -import java.io.Serializable; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.List; /** * @@ -57,16 +56,19 @@ public class MailService implements Serializable { private static final Logger LOGGER = LoggerFactory.getLogger(MailService.class); - @EJB + @Inject private MailTemplateService mailTemplateService; - + @Inject - @AccountConfigurationValue(key = AccountConfigurationKey.BaseUrl) - private String baseUrl; + ConfigService configService; - @Inject - @AccountConfigurationValue(key = AccountConfigurationKey.PasswordResetUrl) - private String passwordResetUrl; +// @Inject +// @AccountConfigurationValue(key = AccountConfigurationKey.BaseUrl) +// private String baseUrl; + +// @Inject +// @AccountConfigurationValue(key = AccountConfigurationKey.PasswordResetUrl) +// private String passwordResetUrl; // TODO make this configurable by injection from the application it uses it, fall back to defaul mail setup if not available @Resource(lookup = "java:jboss/mail/ssgMail") @@ -189,7 +191,7 @@ public class MailService implements Serializable { } } - public String sendPasswortResetStartEmail(AccountEntity account, String token) throws MailException { + public String sendPasswortResetStartEmail(AccountEntity account, String token) throws MailException, ConfigException { MailDatamodel model = new MailDatamodel(account); /* old aproach via FacesContext - add this back as fallback if injection point if not configured @@ -209,10 +211,14 @@ public class MailService implements Serializable { } catch (MalformedURLException ex) { throw new MailException("Error while sending email.", ex); } - String baseUrl = configService.getConfigValue(configKey); + String resetUrlWithToken = baseUrl + "/login.xhtml?token=" + token; */ + + String passwordResetUrl = configService.getConfigValue("backend.passwordreset.url"); + String baseUrl = configService.getConfigValue("backend.base.url"); String resetUrlWithToken = passwordResetUrl + "?token=" + token; + model.addParameter("url", baseUrl); model.addParameter("resetUrl", resetUrlWithToken); return sendHTMLMail(account.getEmailaddress(), "Reset your password", model, "password_reset_html");