package uk.ac.ox.zoo.seeg.abraid.mp.publicsite.web.user.account; import org.joda.time.DateTime; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.transaction.annotation.Transactional; import uk.ac.ox.zoo.seeg.abraid.mp.common.domain.Expert; import uk.ac.ox.zoo.seeg.abraid.mp.common.domain.PasswordResetRequest; import uk.ac.ox.zoo.seeg.abraid.mp.common.domain.ValidatorDiseaseGroup; import uk.ac.ox.zoo.seeg.abraid.mp.common.service.core.DiseaseService; import uk.ac.ox.zoo.seeg.abraid.mp.common.service.core.EmailService; import uk.ac.ox.zoo.seeg.abraid.mp.common.service.core.ExpertService; import uk.ac.ox.zoo.seeg.abraid.mp.publicsite.domain.JsonExpertDetails; import uk.ac.ox.zoo.seeg.abraid.mp.publicsite.validator.ValidationException; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import static ch.lambdaj.Lambda.*; import static org.hamcrest.collection.IsIn.isIn; /** * Helper for the AccountController, separated out into a class to isolate the transaction/exception rollback. * Copyright (c) 2014 University of Oxford */ public class AccountControllerHelper { private static final String FAIL_NO_EXPERT_MATCH = "No matching expert found to update (%s)."; private static final String FAIL_NO_REQUEST_MATCH = "No matching password reset request found (%s)."; private static final String EXPERT_VISIBILITY_EMAIL_DATA_KEY = "expert"; private static final String VISIBILITY_EMAIL_SUBJECT = "Updated user requiring visibility sign off"; private static final String VISIBILITY_EMAIL_TEMPLATE = "account/updatedUserEmail.ftl"; private static final String PASSWORD_RESET_EMAIL_SUBJECT = "ABRAID-MP Password Reset"; private static final String PASSWORD_RESET_EMAIL_TEMPLATE = "account/passwordResetEmail.ftl"; private static final String ID_RESET_EMAIL_DATA_KEY = "id"; private static final String KEY_RESET_EMAIL_DATA_KEY = "key"; private static final String URL_RESET_EMAIL_DATA_KEY = "url"; private final ExpertService expertService; private final DiseaseService diseaseService; private final EmailService emailService; private final PasswordEncoder passwordEncoder; @Autowired public AccountControllerHelper(ExpertService expertService, DiseaseService diseaseService, EmailService emailService, PasswordEncoder passwordEncoder) { this.expertService = expertService; this.diseaseService = diseaseService; this.emailService = emailService; this.passwordEncoder = passwordEncoder; } /** * Updates the database entry for an expert. * @param id The expert to update. * @param expertDto The data to overwrite. * @throws ValidationException Thrown if an id matching expert cannot be found. */ @Transactional(rollbackFor = Exception.class) public void processExpertProfileUpdateAsTransaction(int id, JsonExpertDetails expertDto) throws ValidationException { // Start of transaction Expert expert = expertService.getExpertById(id); if (expert == null) { // Roll back throw new ValidationException(Arrays.asList(String.format(FAIL_NO_EXPERT_MATCH, id))); } else { boolean resetVisibility = !expert.getName().equals(expertDto.getName()) || !expert.getJobTitle().equals(expertDto.getJobTitle()) || !expert.getInstitution().equals(expertDto.getInstitution()) || expert.getVisibilityRequested() != expertDto.getVisibilityRequested(); List<ValidatorDiseaseGroup> allValidatorDiseaseGroups = diseaseService.getAllValidatorDiseaseGroups(); List<ValidatorDiseaseGroup> interests = filter( having(on(ValidatorDiseaseGroup.class).getId(), isIn(expertDto.getDiseaseInterests())), allValidatorDiseaseGroups); expert.setValidatorDiseaseGroups(interests); expert.setName(expertDto.getName()); expert.setJobTitle(expertDto.getJobTitle()); expert.setInstitution(expertDto.getInstitution()); expert.setVisibilityRequested(expertDto.getVisibilityRequested()); if (resetVisibility) { expert.setVisibilityApproved(false); expert.setUpdatedDate(DateTime.now()); } expertService.saveExpert(expert); if (resetVisibility) { sendVisibilityEmailToAdmin(expert); } } // End of transaction } /** * Updates the database entry for an expert's email. * @param id The expert to update. * @param email The new email address. * @throws ValidationException Thrown if an id matching expert cannot be found. */ @Transactional(rollbackFor = Exception.class) public void processExpertEmailChangeAsTransaction(int id, String email) throws ValidationException { // Start of transaction Expert expert = expertService.getExpertById(id); if (expert == null) { // Roll back throw new ValidationException(Arrays.asList(String.format(FAIL_NO_EXPERT_MATCH, id))); } else { expert.setEmail(email); expertService.saveExpert(expert); } // End of transaction } /** * Updates the database entry for an expert's password. * @param id The expert to update. * @param password The new password. * @throws ValidationException Thrown if an id matching expert cannot be found. */ @Transactional(rollbackFor = Exception.class) public void processExpertPasswordChangeAsTransaction(int id, String password) throws ValidationException { // Start of transaction Expert expert = expertService.getExpertById(id); if (expert == null) { // Roll back throw new ValidationException(Arrays.asList(String.format(FAIL_NO_EXPERT_MATCH, id))); } else { String passwordHash = passwordEncoder.encode(password); expert.setPassword(passwordHash); expertService.saveExpert(expert); } // End of transaction } private void sendVisibilityEmailToAdmin(Expert expert) { if (expert.getVisibilityRequested()) { Map<String, Object> data = new HashMap<>(); data.put(EXPERT_VISIBILITY_EMAIL_DATA_KEY, expert); emailService.sendEmailInBackground(VISIBILITY_EMAIL_SUBJECT, VISIBILITY_EMAIL_TEMPLATE, data); } } /** * Updates the database to issue a new password reset request. * @param email The email address of the expert to issue a password reset request for. * @param url The base url of servlet to use in the "reset email". * @throws ValidationException Thrown if an email matching expert cannot be found. */ @Transactional(rollbackFor = Exception.class) public void processExpertPasswordResetRequestAsTransaction(String email, String url) throws ValidationException { // Start of transaction Expert expert = expertService.getExpertByEmail(email); if (expert == null) { // Roll back throw new ValidationException(Arrays.asList(String.format(FAIL_NO_EXPERT_MATCH, email))); } else { String key = PasswordResetRequest.createPasswordResetRequestKey(); Integer id = expertService.createAndSavePasswordResetRequest(email, key); HashMap<String, Object> data = new HashMap<>(); data.put(KEY_RESET_EMAIL_DATA_KEY, key); data.put(ID_RESET_EMAIL_DATA_KEY, id); data.put(URL_RESET_EMAIL_DATA_KEY, url); emailService.sendEmailInBackground( email, PASSWORD_RESET_EMAIL_SUBJECT, PASSWORD_RESET_EMAIL_TEMPLATE, data); } // End of transaction } /** * Updates the database change expert's password using the password reset system. * @param password The new password. * @param id The id of the password reset request. * @throws ValidationException Thrown if an id matching password reset request cannot be found. */ @Transactional(rollbackFor = Exception.class) public void processExpertPasswordResetAsTransaction(String password, Integer id) throws ValidationException { // Start of transaction PasswordResetRequest passwordResetRequest = expertService.getPasswordResetRequest(id); if (passwordResetRequest == null) { // Roll back throw new ValidationException(Arrays.asList(String.format(FAIL_NO_REQUEST_MATCH, id))); } else { String passwordHash = passwordEncoder.encode(password); Expert expert = passwordResetRequest.getExpert(); expert.setPassword(passwordHash); expertService.saveExpert(expert); expertService.deletePasswordResetRequest(passwordResetRequest); } // End of transaction } }