1
2
3
4
5
6
7 package ch.colabproject.colab.api.controller.user;
8
9 import ch.colabproject.colab.api.Helper;
10 import ch.colabproject.colab.api.controller.RequestManager;
11 import ch.colabproject.colab.api.controller.ValidationManager;
12 import ch.colabproject.colab.api.controller.team.InstanceMakerManager;
13 import ch.colabproject.colab.api.controller.team.TeamManager;
14 import ch.colabproject.colab.api.controller.token.TokenManager;
15 import ch.colabproject.colab.api.exceptions.ColabMergeException;
16 import ch.colabproject.colab.api.model.user.Account;
17 import ch.colabproject.colab.api.model.user.AuthInfo;
18 import ch.colabproject.colab.api.model.user.AuthMethod;
19 import ch.colabproject.colab.api.model.user.HashMethod;
20 import ch.colabproject.colab.api.model.user.HttpSession;
21 import ch.colabproject.colab.api.model.user.LocalAccount;
22 import ch.colabproject.colab.api.model.user.SignUpInfo;
23 import ch.colabproject.colab.api.model.user.User;
24 import ch.colabproject.colab.api.persistence.jpa.user.AccountDao;
25 import ch.colabproject.colab.api.persistence.jpa.user.HttpSessionDao;
26 import ch.colabproject.colab.api.persistence.jpa.user.UserDao;
27 import ch.colabproject.colab.api.security.AuthenticationFailure;
28 import ch.colabproject.colab.api.security.SessionManager;
29 import ch.colabproject.colab.generator.model.exceptions.HttpErrorMessage;
30 import ch.colabproject.colab.generator.model.exceptions.MessageI18nKey;
31 import java.time.OffsetDateTime;
32 import java.util.List;
33 import java.util.Optional;
34 import java.util.stream.Collectors;
35 import javax.ejb.LocalBean;
36 import javax.ejb.Stateless;
37 import javax.ejb.TransactionAttribute;
38 import javax.ejb.TransactionAttributeType;
39 import javax.inject.Inject;
40
41 import com.google.common.collect.Lists;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45
46
47
48
49
50 @LocalBean
51 @Stateless
52 public class UserManager {
53
54
55
56
57 private static final int SALT_LENGTH = 32;
58
59
60
61
62 private static final Long AUTHENTICATION_ATTEMPT_MAX = 25L;
63
64
65
66
67 private static final Long AUTHENTICATION_ATTEMPT_RESET_DELAY_SEC = 60 * 15L;
68
69
70 private static final Logger logger = LoggerFactory.getLogger(UserManager.class);
71
72
73
74
75 @Inject
76 private RequestManager requestManager;
77
78
79 @Inject
80 private SessionManager sessionManager;
81
82
83
84
85 @Inject
86 private TokenManager tokenManager;
87
88
89
90
91 @Inject
92 private ValidationManager validationManager;
93
94
95 @Inject
96 private TeamManager teamManager;
97
98
99 @Inject
100 private InstanceMakerManager instanceMakerManager;
101
102
103 @Inject
104 private AccountDao accountDao;
105
106
107 @Inject
108 private UserDao userDao;
109
110
111 @Inject
112 private HttpSessionDao httpSessionDao;
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127 public User assertAndGetUser(Long userId) {
128 User user = userDao.findUser(userId);
129
130 if (user == null) {
131 logger.error("user #{} not found", userId);
132 throw HttpErrorMessage.dataError(MessageI18nKey.DATA_NOT_FOUND);
133 }
134
135 return user;
136 }
137
138
139
140
141
142
143
144
145 public User getUserById(Long id) {
146 return userDao.findUser(id);
147 }
148
149
150
151
152
153
154
155
156 public List<User> getUsersForProject(Long projectId) {
157 logger.debug("Get users of project #{}", projectId);
158
159 List<User> teamMembers = teamManager.getUsersForProject(projectId);
160 List<User> instanceMakers = instanceMakerManager.getUsersForProject(projectId);
161
162 List<User> allUsers = Lists.newArrayList();
163 allUsers.addAll(teamMembers);
164 allUsers.addAll(instanceMakers);
165
166 return allUsers;
167 }
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186 public AuthMethod getAuthenticationMethod(String identifier) {
187 try {
188 return requestManager.sudo(() -> {
189 if (identifier == null || identifier.isBlank()) {
190 throw HttpErrorMessage.badRequest();
191 } else {
192 LocalAccount account = findLocalAccountByIdentifier(identifier);
193
194 if (account != null) {
195 return new AuthMethod(account.getCurrentClientHashMethod(),
196 account.getClientSalt(),
197 account.getNextClientHashMethod(), account.getNewClientSalt());
198 } else {
199
200
201 return this.getDefaultRandomAuthenticationMethod();
202 }
203 }
204 });
205 } catch (Exception e) {
206 return this.getDefaultRandomAuthenticationMethod();
207 }
208 }
209
210
211
212
213
214
215 public AuthMethod getDefaultRandomAuthenticationMethod() {
216 return new AuthMethod(Helper.getDefaultHashMethod(), Helper
217 .generateHexSalt(SALT_LENGTH), null, null);
218 }
219
220
221
222
223
224
225
226
227
228
229 @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
230 public User createAdminUserTx(String username, String email, String plainPassword) {
231 return this.createAdminUser(username, email, plainPassword);
232 }
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247 private User createAdminUser(String username, String email, String plainPassword) {
248 User admin = this.createUserWithLocalAccount(username, username , email, plainPassword);
249
250 LocalAccount account = (LocalAccount) admin.getAccounts().get(0);
251
252 AuthInfo authInfo = new AuthInfo();
253 authInfo.setIdentifier(username);
254 authInfo.setMandatoryHash(
255 Helper.bytesToHex(
256 account.getCurrentClientHashMethod().hash(
257 plainPassword,
258 account.getClientSalt())));
259 this.authenticate(authInfo);
260
261 this.grantAdminRight(admin.getId());
262 return admin;
263 }
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279 private User createUserWithLocalAccount(String username, String firstname, String email, String plainPassword) {
280 AuthMethod method = getDefaultRandomAuthenticationMethod();
281 byte[] hash = method.getMandatoryMethod().hash(plainPassword, method.getSalt());
282
283 SignUpInfo signUpInfo = new SignUpInfo();
284
285 signUpInfo.setUsername(username);
286 signUpInfo.setFirstname(firstname);
287 signUpInfo.setEmail(email);
288 signUpInfo.setHashMethod(method.getMandatoryMethod());
289 signUpInfo.setSalt(method.getSalt());
290 signUpInfo.setHash(Helper.bytesToHex(hash));
291
292 return this.signup(signUpInfo);
293 }
294
295
296
297
298
299
300
301
302
303
304 public User signup(SignUpInfo signup) {
305
306 User user = userDao.findUserByUsername(signup.getUsername());
307 if (user == null) {
308
309 LocalAccount account = accountDao.findLocalAccountByEmail(signup.getEmail());
310
311
312 if (account == null) {
313 if (!Helper.isEmailAddress(signup.getEmail())) {
314 throw HttpErrorMessage.signUpFailed(MessageI18nKey.EMAIL_NOT_VALID);
315 }
316
317 account = new LocalAccount();
318 account.setClientSalt(signup.getSalt());
319 account.setCurrentClientHashMethod(signup.getHashMethod());
320 account.setEmail(signup.getEmail());
321 account.setVerified(false);
322
323 account.setCurrentDbHashMethod(Helper.getDefaultHashMethod());
324 this.shadowHash(account, signup.getHash());
325
326 user = new User();
327 user.getAccounts().add((account));
328 account.setUser(user);
329
330 user.setUsername(signup.getUsername());
331 user.setFirstname(signup.getFirstname());
332 user.setLastname(signup.getLastname());
333 user.setAffiliation(signup.getAffiliation());
334 user.setAgreedTime(OffsetDateTime.now());
335
336 validationManager.assertValid(user);
337 validationManager.assertValid(account);
338
339 User persistedUser = userDao.persistUser(user);
340
341
342 requestManager.flush();
343
344 tokenManager.requestEmailAddressVerification(account, false);
345 return persistedUser;
346 } else {
347
348
349
350
351 throw HttpErrorMessage.signUpFailed(MessageI18nKey.IDENTIFIER_ALREADY_TAKEN);
352 }
353
354 } else {
355
356
357
358 throw HttpErrorMessage.signUpFailed(MessageI18nKey.IDENTIFIER_ALREADY_TAKEN);
359 }
360 }
361
362
363
364
365
366
367
368
369
370
371 public User authenticate(AuthInfo authInfo) {
372 LocalAccount account = findLocalAccountByIdentifier(authInfo.getIdentifier());
373
374 if (account != null) {
375 HashMethod m = account.getCurrentDbHashMethod();
376 String mandatoryHash = authInfo.getMandatoryHash();
377
378 if (mandatoryHash != null) {
379 byte[] hash = m.hash(mandatoryHash, account.getDbSalt());
380
381 AuthenticationFailure aa = sessionManager.getAuthenticationAttempt(account);
382 if (aa != null) {
383 logger.warn("Attempt: {}", aa.getCounter());
384 if (aa.getCounter() >= AUTHENTICATION_ATTEMPT_MAX) {
385
386 OffsetDateTime lastAttempt = aa.getTimestamp();
387 OffsetDateTime delay = lastAttempt
388 .plusSeconds(AUTHENTICATION_ATTEMPT_RESET_DELAY_SEC);
389 if (OffsetDateTime.now().isAfter(delay)) {
390
391 sessionManager.resetAuthenticationAttemptHistory(account);
392 } else {
393
394 logger.warn(
395 "Account {} reached the max number of failed authentication",
396 account);
397 throw HttpErrorMessage.tooManyAttempts();
398 }
399 }
400 }
401
402
403
404
405 if (Helper.constantTimeArrayEquals(hash, account.getHashedPassword())) {
406
407
408 sessionManager.resetAuthenticationAttemptHistory(account);
409
410 boolean forceShadow = false;
411
412
413 if (account.getNextClientHashMethod() != null
414 && authInfo.getOptionalHash() != null) {
415
416 account.setClientSalt(account.getNewClientSalt());
417 account.setNewClientSalt(null);
418 account.setCurrentClientHashMethod(account.getNextClientHashMethod());
419 account.setNextClientHashMethod(null);
420
421
422 mandatoryHash = authInfo.getOptionalHash();
423 forceShadow = true;
424 }
425
426
427 if (account.getNextDbHashMethod() != null) {
428
429 account.setCurrentDbHashMethod(account.getNextDbHashMethod());
430 account.setNextDbHashMethod(null);
431
432 forceShadow = true;
433 }
434
435 if (forceShadow) {
436 this.shadowHash(account, mandatoryHash);
437 }
438
439 requestManager.login(account);
440
441 return account.getUser();
442 } else {
443
444 sessionManager.authenticationFailure(account);
445 }
446 }
447 }
448
449
450
451
452 throw HttpErrorMessage.authenticationFailed();
453 }
454
455
456
457
458
459
460
461
462 public void updatePassword(AuthInfo authInfo) {
463 LocalAccount account = findLocalAccountByIdentifier(authInfo.getIdentifier());
464
465 if (account != null) {
466 String mandatoryHash = authInfo.getMandatoryHash();
467
468 if (mandatoryHash != null) {
469
470
471 if (account.getNextDbHashMethod() != null) {
472
473 account.setCurrentDbHashMethod(account.getNextDbHashMethod());
474 account.setNextDbHashMethod(null);
475
476 }
477
478 this.shadowHash(account, mandatoryHash);
479 return;
480 }
481 }
482
483 throw HttpErrorMessage.forbidden();
484 }
485
486
487
488
489
490
491
492 private void shadowHash(LocalAccount account, String hash) {
493
494 account.setDbSalt(Helper.generateSalt(SALT_LENGTH));
495
496 byte[] newHash = account.getCurrentDbHashMethod().hash(hash, account.getDbSalt());
497 account.setHashedPassword(newHash);
498 }
499
500
501
502
503 public void logout() {
504
505 this.requestManager.logout();
506 }
507
508
509
510
511
512
513 public void forceLogout(Long sessionId) {
514 HttpSession httpSession = httpSessionDao.findHttpSession(sessionId);
515 HttpSession currentSession = requestManager.getHttpSession();
516 if (httpSession != null) {
517 if (!httpSession.equals(currentSession)) {
518 requestManager.sudo(() -> sessionManager.deleteHttpSession(httpSession));
519 } else {
520 throw HttpErrorMessage.badRequest();
521 }
522 }
523 }
524
525
526
527
528
529
530 public void grantAdminRight(User user) {
531 user.setAdmin(true);
532 }
533
534
535
536
537
538
539 public void grantAdminRight(Long id) {
540 this.grantAdminRight(userDao.findUser(id));
541 }
542
543
544
545
546
547
548 public void revokeAdminRight(Long id) {
549 this.revokeAdminRight(userDao.findUser(id));
550 }
551
552
553
554
555
556
557 public void revokeAdminRight(User user) {
558 if (user != null) {
559 User currentUser = requestManager.getCurrentUser();
560 if (user.equals(currentUser)) {
561
562 throw HttpErrorMessage.badRequest();
563 } else {
564 user.setAdmin(false);
565 }
566 }
567 }
568
569
570
571
572
573
574 public void switchClientHashMethod(Long id) {
575 Account account = accountDao.findAccount(id);
576 if (account instanceof LocalAccount) {
577 LocalAccount localAccount = (LocalAccount) account;
578 AuthMethod authMethod = getDefaultRandomAuthenticationMethod();
579 localAccount.setNextClientHashMethod(authMethod.getMandatoryMethod());
580 localAccount.setNewClientSalt(authMethod.getSalt());
581 }
582 }
583
584
585
586
587
588
589 public void switchServerHashMethod(Long id) {
590 Account account = accountDao.findAccount(id);
591 if (account instanceof LocalAccount) {
592 LocalAccount localAccount = (LocalAccount) account;
593 AuthMethod authMethod = getDefaultRandomAuthenticationMethod();
594 localAccount.setNextDbHashMethod(authMethod.getMandatoryMethod());
595 }
596 }
597
598
599
600
601
602
603
604
605 public LocalAccount findLocalAccountByIdentifier(String identifier) {
606 LocalAccount account = accountDao.findLocalAccountByEmail(identifier);
607
608 if (account == null) {
609
610
611 User user = userDao.findUserByUsername(identifier);
612 if (user != null) {
613
614
615 Optional<Account> optAccount = user.getAccounts().stream()
616 .filter(a -> a instanceof LocalAccount)
617 .findFirst();
618 if (optAccount.isPresent()) {
619 account = (LocalAccount) optAccount.get();
620 }
621 }
622 }
623 return account;
624 }
625
626
627
628
629
630
631
632 public void requestPasswordReset(String email) {
633 logger.debug("Request reset password: {}", email);
634 LocalAccount account = accountDao.findLocalAccountByEmail(email);
635 if (account != null) {
636
637 tokenManager.sendResetPasswordToken(account, true);
638 }
639 }
640
641
642
643
644
645
646
647
648
649
650
651
652 public LocalAccount updateLocalAccountEmailAddress(LocalAccount account)
653 throws ColabMergeException {
654 logger.debug("Update LocalAccount email address: {}", account);
655 LocalAccount managedAccount = (LocalAccount) accountDao.findAccount(account.getId());
656
657 String currentEmail = managedAccount.getEmail();
658 String newEmail = account.getEmail();
659
660 if (newEmail != null && !newEmail.equals(currentEmail)) {
661 if (!Helper.isEmailAddress(newEmail)) {
662 throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
663 }
664
665 try {
666 managedAccount.setVerified(false);
667 managedAccount.setEmail(newEmail);
668
669 requestManager.flush();
670 tokenManager.requestEmailAddressVerification(account, false);
671 } catch (Exception e) {
672
673 logger.error("Exception", e);
674 }
675 }
676
677 return managedAccount;
678 }
679
680
681
682
683
684
685 public void setLocalAccountAsVerified(LocalAccount account) {
686 account.setVerified(Boolean.TRUE);
687 }
688
689
690
691
692
693
694 public void updateUserAgreedTime(Long userId) {
695 User user = assertAndGetUser(userId);
696 OffsetDateTime now = OffsetDateTime.now();
697 user.setAgreedTime(now);
698 }
699
700
701
702
703
704
705 public List<HttpSession> getCurrentUserActiveHttpSessions() {
706 return requestManager.getCurrentUser().getAccounts().stream()
707 .flatMap(account -> account.getHttpSessions().stream()).collect(Collectors.toList());
708 }
709 }