Project

General

Profile

Download (10.3 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 static org.junit.Assert.assertEquals;
12
import static org.junit.Assert.assertNotNull;
13
import static org.junit.Assert.assertTrue;
14

    
15
import java.io.FileNotFoundException;
16
import java.time.Duration;
17
import java.util.concurrent.CountDownLatch;
18
import java.util.regex.Matcher;
19
import java.util.regex.Pattern;
20

    
21
import javax.mail.internet.AddressException;
22
import javax.mail.internet.MimeMessage;
23

    
24
import org.apache.log4j.Level;
25
import org.apache.log4j.Logger;
26
import org.junit.After;
27
import org.junit.Before;
28
import org.junit.Test;
29
import org.springframework.beans.factory.annotation.Autowired;
30
import org.springframework.core.env.Environment;
31
import org.springframework.mail.javamail.JavaMailSender;
32
import org.springframework.util.concurrent.ListenableFuture;
33
import org.subethamail.wiser.Wiser;
34
import org.subethamail.wiser.WiserMessage;
35
import org.unitils.dbunit.annotation.DataSet;
36
import org.unitils.spring.annotation.SpringBeanByName;
37
import org.unitils.spring.annotation.SpringBeanByType;
38

    
39
import eu.etaxonomy.cdm.api.security.AbstractRequestTokenStore;
40
import eu.etaxonomy.cdm.api.security.AccountCreationRequest;
41
import eu.etaxonomy.cdm.api.security.IAbstractRequestTokenStore;
42
import eu.etaxonomy.cdm.api.security.PasswordResetRequest;
43
import eu.etaxonomy.cdm.api.service.IUserService;
44
import eu.etaxonomy.cdm.test.unitils.CleanSweepInsertLoadStrategy;
45

    
46

    
47
public class AccountRegistrationServiceTest extends eu.etaxonomy.cdm.test.integration.CdmTransactionalIntegrationTest {
48

    
49
    private static final double maxRequestRate = 4.0;
50

    
51
    Logger logger = Logger.getLogger(AccountRegistrationServiceTest.class);
52

    
53
    private static final int rateLimiterTimeout = 200;
54
    private static final String userName = "pwdResetTestUser";
55
    private static final String userPWD = "super_SECURE_123";
56
    private static final String userEmail = "pwdResetTestUser@cybertaxonomy.test";
57

    
58

    
59
    private static String base64UrlSaveCharClass = "[a-zA-Z0-9\\-_]";
60

    
61

    
62
    private static final String requestFormUrlTemplate = "http://cybertaxonomy.test/passwordReset?userName={%s}&sessID=f8d8sf8dsf";
63

    
64
    @SpringBeanByType
65
    private IUserService userService;
66

    
67
    @SpringBeanByType
68
    private IAccountRegistrationService accountRegistrationService;
69

    
70
    @SpringBeanByName
71
    private IAbstractRequestTokenStore<AccountCreationRequest> accountCreationRequestTokenStore;
72

    
73
    @SpringBeanByType
74
    private JavaMailSender emailSender;
75

    
76
    @Autowired
77
    private Environment env;
78

    
79
    private Wiser wiser = null;
80

    
81
    private CountDownLatch createRequestTokenSendSignal;
82
    private CountDownLatch accountCreatedSignal;
83
    Throwable assyncError = null;
84

    
85
    @Before
86
    public void startEmailServer() {
87
        // Integer smtpPort = env.getProperty(SendEmailConfigurer.PORT, Integer.class);
88
        wiser = new Wiser();
89
        wiser.setPort(2500); // must be the same as configured for SendEmailConfigurer.PORT
90
        wiser.start();
91
        logger.debug("Wiser email server started");
92
    }
93

    
94

    
95
    @Before
96
    public void accountRegistrationService() throws InterruptedException {
97
        logger.setLevel(Level.DEBUG);
98
        Logger.getLogger(PasswordResetRequest.class).setLevel(Level.TRACE);
99
        // speed up testing
100
        accountRegistrationService.setRateLimiterTimeout(Duration.ofMillis(rateLimiterTimeout));
101
        accountRegistrationService.setRate(maxRequestRate);
102
        // pause long enough to avoid conflicts
103
        long sleepTime = Math.round(1000 / maxRequestRate) + rateLimiterTimeout;
104
        Thread.sleep(sleepTime);
105
    }
106

    
107
    @Before
108
    public void resetAsyncVars() {
109
        assyncError = null;
110
        createRequestTokenSendSignal = null;
111
        accountCreatedSignal = null;
112
    }
113

    
114
    @After
115
    public void stopEmailServer() {
116
        wiser.stop();
117
    }
118

    
119
    @Test
120
    @DataSet(loadStrategy = CleanSweepInsertLoadStrategy.class, value="/eu/etaxonomy/cdm/database/ClearDBDataSet.xml")
121
    public void testSuccessfulEmailReset() throws Throwable {
122

    
123
        logger.debug("testSuccessfulEmailReset() ...");
124

    
125
        // printDataSet(System.err, "UserAccount");
126

    
127
        createRequestTokenSendSignal = new CountDownLatch(1);
128
        accountCreatedSignal = new CountDownLatch(1);
129

    
130
        ListenableFuture<Boolean> emailResetFuture = accountRegistrationService.emailAccountRegistrationRequest(userEmail, userName, userPWD, requestFormUrlTemplate);
131
        emailResetFuture.addCallback(
132
                requestSuccessVal -> {
133
                    createRequestTokenSendSignal.countDown();
134
                }, futureException -> {
135
                    assyncError = futureException;
136
                    createRequestTokenSendSignal.countDown();
137
                });
138

    
139
        // -- wait for passwordResetService.emailResetToken() to complete
140
        createRequestTokenSendSignal.await();
141

    
142
        if(assyncError != null) {
143
            throw assyncError;
144
        }
145

    
146
        assertNotNull(emailResetFuture.get());
147
        assertEquals(1, wiser.getMessages().size());
148

    
149
        // -- read email message
150
        WiserMessage requestMessage = wiser.getMessages().get(0);
151
        MimeMessage requestMimeMessage = requestMessage.getMimeMessage();
152

    
153
        assertTrue(requestMimeMessage.getSubject()
154
                .matches(UserAccountEmailTemplates.REGISTRATION_REQUEST_EMAIL_SUBJECT_TEMPLATE.replace("${dataBase}", ".*"))
155
                );
156

    
157
        String messageContent = requestMimeMessage.getContent().toString();
158
        // -- extract token
159
        Pattern pattern = Pattern.compile("=\\{(" + base64UrlSaveCharClass + "+)\\}");
160
        Matcher m = pattern.matcher(messageContent);
161
        assertTrue(m.find());
162
        assertEquals(AbstractRequestTokenStore.TOKEN_LENGTH + 17, m.group(1).length());
163

    
164
        // -- change password
165
        ListenableFuture<Boolean> createAccountFuture = accountRegistrationService.createUserAccount(m.group(1), "Testor", "Nutzer", "Dr.");
166
        createAccountFuture.addCallback(requestSuccessVal -> {
167
            accountCreatedSignal.countDown();
168
        }, futureException -> {
169
            assyncError =  futureException;
170
            accountCreatedSignal.countDown();
171
        });
172
        // -- wait for passwordResetService.resetPassword to complete
173
        accountCreatedSignal.await();
174

    
175
        assertTrue(createAccountFuture.get());
176
        assertEquals(2, wiser.getMessages().size());
177
        WiserMessage successMessage = wiser.getMessages().get(1);
178
        MimeMessage successMimeMessage = successMessage.getMimeMessage();
179
        assertEquals(UserAccountEmailTemplates.REGISTRATION_SUCCESS_EMAIL_SUBJECT_TEMPLATE.replace("${userName}", userName), successMimeMessage.getSubject());
180
    }
181

    
182
    @Test
183
    @DataSet(loadStrategy = CleanSweepInsertLoadStrategy.class, value="/eu/etaxonomy/cdm/database/ClearDBDataSet.xml")
184
    public void emailResetToken_ivalidEmailAddress() throws Throwable {
185

    
186
        logger.debug("emailResetToken_ivalidEmailAddress() ...");
187

    
188
        createRequestTokenSendSignal = new CountDownLatch(1);
189

    
190
        accountRegistrationService.setRateLimiterTimeout(Duration.ofMillis(1)); // as should as possible to allow the fist call to be successful (with 1ns the fist call fails!)
191
        ListenableFuture<Boolean> emailResetFuture = accountRegistrationService.emailAccountRegistrationRequest("not-a-valid-email@#address#", userName, userPWD, requestFormUrlTemplate);
192
        emailResetFuture.addCallback(
193
                requestSuccessVal -> {
194
                    createRequestTokenSendSignal.countDown();
195
                }, futureException -> {
196
                    assyncError = futureException;
197
                    createRequestTokenSendSignal.countDown();
198
                });
199

    
200

    
201
        // -- wait for passwordResetService.emailResetToken() to complete
202
        createRequestTokenSendSignal.await();
203

    
204
        assertNotNull(assyncError);
205
        assertEquals(AddressException.class, assyncError.getClass());
206
    }
207

    
208
    @Test
209
    @DataSet(loadStrategy = CleanSweepInsertLoadStrategy.class, value="/eu/etaxonomy/cdm/database/ClearDBDataSet.xml")
210
    public void testInvalidToken() throws Throwable {
211

    
212
        logger.debug("testInvalidToken() ...");
213

    
214
        accountCreatedSignal = new CountDownLatch(1);
215

    
216
        // -- change password
217
        ListenableFuture<Boolean> resetPasswordFuture = accountRegistrationService.createUserAccount("IUER9843URIO--INVALID-TOKEN--UWEUR89EUWWEOIR", userName, null, null);
218
        resetPasswordFuture.addCallback(requestSuccessVal -> {
219
            accountCreatedSignal.countDown();
220
        }, futureException -> {
221
            assyncError =  futureException;
222
            accountCreatedSignal.countDown();
223
        });
224
        // -- wait for passwordResetService.resetPassword to complete
225
        accountCreatedSignal.await();
226

    
227
        assertNotNull(assyncError);
228
        assertEquals(AccountSelfManagementException.class, assyncError.getClass());
229
        assertEquals(0, wiser.getMessages().size());
230
    }
231

    
232
    // @Test
233
    @DataSet(loadStrategy = CleanSweepInsertLoadStrategy.class, value="/eu/etaxonomy/cdm/database/ClearDBDataSet.xml")
234
    public void testUserNameExists() throws Throwable {
235

    
236
        logger.debug("testUserNameExists() ...");
237

    
238
        createRequestTokenSendSignal = new CountDownLatch(1);
239

    
240
        ListenableFuture<Boolean> emailResetFuture = accountRegistrationService.emailAccountRegistrationRequest(userEmail, "admin", userPWD, requestFormUrlTemplate);
241
        emailResetFuture.addCallback(
242
                requestSuccessVal -> {
243
                    createRequestTokenSendSignal.countDown();
244
                }, futureException -> {
245
                    assyncError = futureException;
246
                    createRequestTokenSendSignal.countDown();
247
                });
248

    
249
        // -- wait for passwordResetService.emailResetToken() to complete
250
        createRequestTokenSendSignal.await();
251

    
252
        assertNotNull(assyncError);
253
        assertEquals(AccountSelfManagementException.class, assyncError.getClass());
254
        assertEquals(AccountRegistrationService.USER_NAME_EXISTS_MSG, assyncError.getMessage());
255
        assertEquals(0, wiser.getMessages().size());
256
    }
257

    
258
    @Override
259
    public void createTestDataSet() throws FileNotFoundException {
260
        // not needed
261
    }
262

    
263
}
(1-1/2)