Project

General

Profile

Download (6.4 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2021 EDIT
3
* European Distributed Institute of Taxonomy
4
* http://www.e-taxonomy.eu
5
*
6
* The contents of this file are subject to the Mozilla Public License Version 1.1
7
* See LICENSE.TXT at the top of this package for the full license terms.
8
*/
9
package eu.etaxonomy.cdm.api.service.security;
10

    
11
import java.util.HashMap;
12
import java.util.Map;
13
import java.util.Optional;
14

    
15
import javax.mail.internet.AddressException;
16
import javax.mail.internet.InternetAddress;
17

    
18
import org.springframework.beans.factory.annotation.Autowired;
19
import org.springframework.beans.factory.annotation.Qualifier;
20
import org.springframework.dao.DataAccessException;
21
import org.springframework.mail.MailException;
22
import org.springframework.scheduling.annotation.Async;
23
import org.springframework.scheduling.annotation.AsyncResult;
24
import org.springframework.security.core.userdetails.UserDetails;
25
import org.springframework.security.core.userdetails.UsernameNotFoundException;
26
import org.springframework.stereotype.Service;
27
import org.springframework.transaction.annotation.Transactional;
28
import org.springframework.util.Assert;
29
import org.springframework.util.concurrent.ListenableFuture;
30

    
31
import eu.etaxonomy.cdm.api.security.AbstractRequestToken;
32
import eu.etaxonomy.cdm.api.security.IAbstractRequestTokenStore;
33
import eu.etaxonomy.cdm.api.security.PasswordResetRequest;
34
import eu.etaxonomy.cdm.model.permission.User;
35

    
36
/**
37
 * @author a.kohlbecker
38
 * @since Oct 26, 2021
39
 */
40
@Service
41
@Transactional(readOnly = false)
42
public class PasswordResetService extends AccountSelfManagementService implements IPasswordResetService {
43

    
44
    @Autowired
45
    @Qualifier("passwordResetTokenStore")
46
    IAbstractRequestTokenStore<PasswordResetRequest, User> passwordResetTokenStore;
47

    
48
    @Override
49
    @Async
50
    public ListenableFuture<Boolean> emailResetToken(String userNameOrEmail, String passwordRequestFormUrlTemplate) throws MailException, EmailAddressNotFoundException {
51

    
52
        try {
53
            // give calling methods an bit time to register the Listeners to the Future
54
            Thread.sleep(10);
55
        } catch (InterruptedException e1) {
56
            // IGNORE
57
        }
58
        if(logger.isTraceEnabled()) {
59
            logger.trace("emailResetToken trying to aquire from rate limiter [rate: " + emailResetToken_rateLimiter.getRate() + ", timeout: " + getRateLimiterTimeout().toMillis() + "ms]");
60
        }
61
        if (emailResetToken_rateLimiter.tryAcquire(getRateLimiterTimeout())) {
62
            logger.trace("emailResetToken allowed by rate limiter");
63
            try {
64
                User user = findUser(userNameOrEmail);
65
                AbstractRequestToken resetRequest = passwordResetTokenStore.create(user.getEmailAddress(), user);
66
                String passwordRequestFormUrl = String.format(passwordRequestFormUrlTemplate, resetRequest.getToken());
67
                Map<String, String> additionalValues = new HashMap<>();
68
                additionalValues.put("linkUrl", passwordRequestFormUrl);
69
                sendEmail(user.getEmailAddress(), user.getUsername(),
70
                        UserAccountEmailTemplates.RESET_REQUEST_EMAIL_SUBJECT_TEMPLATE,
71
                        UserAccountEmailTemplates.REGISTRATION_REQUEST_EMAIL_BODY_TEMPLATE, additionalValues);
72
                logger.info("A password reset request for  " + user.getUsername() + " has been send to "
73
                        + user.getEmailAddress());
74
            } catch (EmailAddressNotFoundException e) {
75
                throw e;
76
            } catch (UsernameNotFoundException e) {
77
                logger.warn("Password reset request for unknown user, cause: " + e.getMessage());
78
            } catch (MailException e) {
79
                throw e;
80
            }
81
            return new AsyncResult<Boolean>(true);
82
        } else {
83
            logger.trace("blocked by rate limiter");
84
            return new AsyncResult<Boolean>(false);
85
        }
86
    }
87

    
88
   @Override
89
   @Async
90
   public ListenableFuture<Boolean> resetPassword(String token, String newPassword) throws AccountSelfManagementException, MailException {
91

    
92
       if (resetPassword_rateLimiter.tryAcquire(getRateLimiterTimeout())) {
93

    
94
           Optional<PasswordResetRequest> resetRequest = passwordResetTokenStore.findRequest(token);
95
           if (resetRequest.isPresent()) {
96
               try {
97
                   UserDetails user = userService.loadUserByUsername(resetRequest.get().getUserName());
98
                   Assert.isAssignable(user.getClass(), User.class);
99
                   userService.encodeUserPassword((User)user, newPassword);
100
                   userDao.saveOrUpdate((User)user);
101
                   passwordResetTokenStore.remove(token);
102
                   sendEmail(resetRequest.get().getUserEmail(), resetRequest.get().getUserName(),
103
                           UserAccountEmailTemplates.RESET_SUCCESS_EMAIL_SUBJECT_TEMPLATE,
104
                           UserAccountEmailTemplates.RESET_SUCCESS_EMAIL_BODY_TEMPLATE, null);
105
                   return new AsyncResult<Boolean>(true);
106
               } catch (DataAccessException | IllegalArgumentException | UsernameNotFoundException e) {
107
                   logger.error("Failed to change password of User " + resetRequest.get().getUserName(), e);
108
                   sendEmail(resetRequest.get().getUserEmail(), resetRequest.get().getUserName(),
109
                           UserAccountEmailTemplates.RESET_FAILED_EMAIL_SUBJECT_TEMPLATE,
110
                           UserAccountEmailTemplates.RESET_FAILED_EMAIL_BODY_TEMPLATE, null);
111
               }
112
           } else {
113
               throw new AccountSelfManagementException("Invalid password reset token");
114
           }
115
       }
116
       return new AsyncResult<Boolean>(false);
117
   }
118

    
119
    protected User findUser(String userNameOrEmail) throws UsernameNotFoundException, EmailAddressNotFoundException {
120

    
121
        User user;
122
        try {
123
            InternetAddress emailAddr = new InternetAddress(userNameOrEmail);
124
            emailAddr.validate();
125
            user = userDao.findByEmailAddress(userNameOrEmail);
126
            if (user == null) {
127
                throw new EmailAddressNotFoundException(
128
                        "No user with the email address'" + userNameOrEmail + "' found.");
129
            }
130
        } catch (AddressException ex) {
131
            user = userDao.findUserByUsername(userNameOrEmail);
132
            if (user == null) {
133
                throw new UsernameNotFoundException("No user with the user name: '" + userNameOrEmail + "' found.");
134
            }
135
        }
136
        return user;
137
    }
138
}
(9-9/10)