1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.mycore.user2;
20
21 import java.security.NoSuchAlgorithmException;
22 import java.security.SecureRandom;
23 import java.util.ArrayList;
24 import java.util.Base64;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.List;
28 import java.util.Optional;
29 import java.util.stream.Stream;
30
31 import org.apache.commons.lang3.StringUtils;
32 import org.apache.logging.log4j.LogManager;
33 import org.apache.logging.log4j.Logger;
34 import org.mycore.access.MCRAccessManager;
35 import org.mycore.backend.jpa.MCREntityManagerProvider;
36 import org.mycore.common.MCRException;
37 import org.mycore.common.MCRSession;
38 import org.mycore.common.MCRSessionMgr;
39 import org.mycore.common.MCRSystemUserInformation;
40 import org.mycore.common.MCRUserInformation;
41 import org.mycore.common.MCRUtils;
42 import org.mycore.common.config.MCRConfiguration2;
43 import org.mycore.common.events.MCREvent;
44 import org.mycore.common.events.MCREventManager;
45 import org.mycore.common.xml.MCRXMLFunctions;
46 import org.mycore.datamodel.classifications2.MCRCategoryID;
47 import org.mycore.datamodel.common.MCRISO8601Format;
48
49 import jakarta.persistence.EntityManager;
50 import jakarta.persistence.NoResultException;
51 import jakarta.persistence.TypedQuery;
52 import jakarta.persistence.criteria.CriteriaBuilder;
53 import jakarta.persistence.criteria.CriteriaQuery;
54 import jakarta.persistence.criteria.Join;
55 import jakarta.persistence.criteria.JoinType;
56 import jakarta.persistence.criteria.Predicate;
57 import jakarta.persistence.criteria.Root;
58 import jakarta.persistence.metamodel.SingularAttribute;
59
60
61
62
63
64
65
66 public class MCRUserManager {
67
68 private static final int HASH_ITERATIONS = MCRConfiguration2
69 .getInt(MCRUser2Constants.CONFIG_PREFIX + "HashIterations").orElse(1000);
70
71 private static final Logger LOGGER = LogManager.getLogger();
72
73 private static final SecureRandom SECURE_RANDOM;
74
75 static {
76 try {
77 SECURE_RANDOM = SecureRandom.getInstance("SHA1PRNG");
78 } catch (NoSuchAlgorithmException e) {
79 throw new MCRException("Could not initialize secure SECURE_RANDOM number", e);
80 }
81 }
82
83
84 static String table;
85
86
87
88
89
90
91
92 public static MCRUser getUser(String userName) {
93 if (!userName.contains("@")) {
94 return getUser(userName, MCRRealmFactory.getLocalRealm());
95 } else {
96 String[] parts = userName.split("@");
97 if (parts.length == 2) {
98 return getUser(parts[0], parts[1]);
99 } else {
100 return null;
101 }
102 }
103 }
104
105
106
107
108
109
110
111
112 public static MCRUser getUser(String userName, MCRRealm realm) {
113 return getUser(userName, realm.getID());
114 }
115
116
117
118
119
120
121
122
123 public static MCRUser getUser(String userName, String realmId) {
124 EntityManager em = MCREntityManagerProvider.getCurrentEntityManager();
125 return getByNaturalID(em, userName, realmId)
126 .map(MCRUserManager::setRoles)
127 .orElseGet(() -> {
128 LOGGER.warn("Could not find requested user: {}@{}", userName, realmId);
129 return null;
130 });
131 }
132
133
134
135
136
137
138 public static Stream<MCRUser> getUsers(String attrName, String attrValue) {
139 EntityManager em = MCREntityManagerProvider.getCurrentEntityManager();
140 TypedQuery<MCRUser> propertyQuery = em.createNamedQuery("MCRUser.byPropertyValue", MCRUser.class);
141 propertyQuery.setParameter("name", attrName);
142 propertyQuery.setParameter("value", attrValue);
143 MCRUserAttribute attr = new MCRUserAttribute(attrName, attrValue);
144 return propertyQuery.getResultList()
145 .stream()
146 .filter(u -> u.getAttributes().contains(attr))
147 .peek(em::refresh);
148 }
149
150 private static MCRUser setRoles(MCRUser mcrUser) {
151 Collection<MCRCategoryID> roleIDs = MCRRoleManager.getRoleIDs(mcrUser);
152 mcrUser.getSystemRoleIDs().clear();
153 mcrUser.getExternalRoleIDs().clear();
154 for (MCRCategoryID roleID : roleIDs) {
155 if (roleID.getRootID().equals(MCRUser2Constants.ROLE_CLASSID.getRootID())) {
156 mcrUser.getSystemRoleIDs().add(roleID.getID());
157 } else {
158 mcrUser.getExternalRoleIDs().add(roleID.toString());
159 }
160 }
161 return mcrUser;
162 }
163
164
165
166
167
168
169
170 public static boolean exists(String userName) {
171 return exists(userName, MCRRealmFactory.getLocalRealm());
172 }
173
174
175
176
177
178
179
180
181 public static boolean exists(String userName, MCRRealm realm) {
182 return exists(userName, realm.getID());
183 }
184
185
186
187
188
189
190
191
192 public static boolean exists(String userName, String realm) {
193 EntityManager em = MCREntityManagerProvider.getCurrentEntityManager();
194 CriteriaBuilder cb = em.getCriteriaBuilder();
195 CriteriaQuery<Number> query = cb.createQuery(Number.class);
196 Root<MCRUser> users = query.from(MCRUser.class);
197 return em.createQuery(
198 query
199 .select(cb.count(users))
200 .where(getUserRealmCriterion(cb, users, userName, realm)))
201 .getSingleResult().intValue() > 0;
202 }
203
204
205
206
207
208
209
210 public static void createUser(MCRUser user) {
211 if (isInvalidUser(user)) {
212 throw new MCRException("User is invalid: " + user.getUserID());
213 }
214
215 if (user instanceof MCRTransientUser) {
216 createUser((MCRTransientUser) user);
217 return;
218 }
219
220 EntityManager em = MCREntityManagerProvider.getCurrentEntityManager();
221 em.persist(user);
222 LOGGER.info(() -> "user saved: " + user.getUserID());
223 MCRRoleManager.storeRoleAssignments(user);
224 MCREvent evt = new MCREvent(MCREvent.ObjectType.USER, MCREvent.EventType.CREATE);
225 evt.put(MCREvent.USER_KEY, user);
226 MCREventManager.instance().handleEvent(evt);
227 }
228
229
230
231
232
233
234
235 public static void createUser(MCRTransientUser user) {
236 if (isInvalidUser(user)) {
237 throw new MCRException("User is invalid: " + user.getUserID());
238 }
239
240 createUser(user.clone());
241 }
242
243
244
245
246
247
248
249
250 public static boolean isInvalidUser(MCRUser user) {
251 if (MCRSystemUserInformation.getGuestInstance().getUserID().equals(user.getUserID())) {
252 return true;
253 }
254 return MCRSystemUserInformation.getSystemUserInstance().getUserID().equals(user.getUserID());
255 }
256
257
258
259
260
261
262
263 public static void updateUser(MCRUser user) {
264 if (isInvalidUser(user)) {
265 throw new MCRException("User is invalid: " + user.getUserID());
266 }
267 EntityManager em = MCREntityManagerProvider.getCurrentEntityManager();
268 Optional<MCRUser> inDb = getByNaturalID(em, user.getUserName(), user.getRealmID());
269 if (!inDb.isPresent()) {
270 createUser(user);
271 return;
272 }
273 inDb.ifPresent(db -> {
274 user.internalID = db.internalID;
275 em.detach(db);
276 em.merge(user);
277 MCRRoleManager.unassignRoles(user);
278 MCRRoleManager.storeRoleAssignments(user);
279 MCREvent evt = new MCREvent(MCREvent.ObjectType.USER, MCREvent.EventType.UPDATE);
280 evt.put(MCREvent.USER_KEY, user);
281 MCREventManager.instance().handleEvent(evt);
282 });
283 }
284
285
286
287
288
289
290 public static void deleteUser(String userName) {
291 if (!userName.contains("@")) {
292 deleteUser(userName, MCRRealmFactory.getLocalRealm());
293 } else {
294 String[] parts = userName.split("@");
295 deleteUser(parts[0], parts[1]);
296 }
297 }
298
299
300
301
302
303
304
305 public static void deleteUser(String userName, MCRRealm realm) {
306 deleteUser(userName, realm.getID());
307 }
308
309
310
311
312
313
314
315 public static void deleteUser(String userName, String realmId) {
316 MCRUser user = getUser(userName, realmId);
317 MCREvent evt = new MCREvent(MCREvent.ObjectType.USER, MCREvent.EventType.DELETE);
318 evt.put(MCREvent.USER_KEY, user);
319 MCREventManager.instance().handleEvent(evt);
320 MCRRoleManager.unassignRoles(user);
321 EntityManager em = MCREntityManagerProvider.getCurrentEntityManager();
322 em.remove(user);
323 }
324
325
326
327
328
329
330 public static void deleteUser(MCRUser user) {
331 deleteUser(user.getUserName(), user.getRealmID());
332 }
333
334
335
336
337
338
339 public static List<MCRUser> listUsers(MCRUser owner) {
340 EntityManager em = MCREntityManagerProvider.getCurrentEntityManager();
341 CriteriaBuilder cb = em.getCriteriaBuilder();
342 CriteriaQuery<MCRUser> query = cb.createQuery(MCRUser.class);
343 Root<MCRUser> users = query.from(MCRUser.class);
344 users.fetch(MCRUser_.owner);
345 return em.createQuery(
346 query
347 .distinct(true)
348 .where(cb.equal(users.get(MCRUser_.owner), owner)))
349 .getResultList();
350 }
351
352 private static String buildSearchPattern(String searchPattern) {
353 searchPattern = searchPattern.replace('*', '%');
354 searchPattern = searchPattern.replace('?', '_');
355 return searchPattern.toLowerCase(MCRSessionMgr.getCurrentSession().getLocale());
356 }
357
358 private static boolean isValidSearchPattern(String searchPattern) {
359 return StringUtils.isNotEmpty(searchPattern);
360 }
361
362 private static Predicate[] buildCondition(CriteriaBuilder cb, Root<MCRUser> root, String userPattern, String realm,
363 String namePattern, String mailPattern, String attributeNamePattern) {
364
365 ArrayList<Predicate> predicates = new ArrayList<>(2);
366 addEqualsPredicate(cb, root, MCRUser_.realmID, realm, predicates);
367
368 ArrayList<Predicate> searchPredicates = new ArrayList<>(3);
369 addSearchPredicate(cb, root, MCRUser_.userName, userPattern, searchPredicates);
370 addSearchPredicate(cb, root, MCRUser_.realName, namePattern, searchPredicates);
371 addSearchPredicate(cb, root, MCRUser_.EMail, mailPattern, searchPredicates);
372
373 if (isValidSearchPattern(attributeNamePattern)) {
374 Join<MCRUser, MCRUserAttribute> userAttributeJoin = root.join(MCRUser_.attributes);
375 searchPredicates.add(cb.like(userAttributeJoin.get(MCRUserAttribute_.name),
376 buildSearchPattern(attributeNamePattern)));
377 }
378
379 if (!searchPredicates.isEmpty()) {
380 if (1 == searchPredicates.size()) {
381 predicates.add(searchPredicates.get(0));
382 } else {
383 predicates.add(cb.or(searchPredicates.toArray(new Predicate[searchPredicates.size()])));
384 }
385 }
386
387 return predicates.toArray(new Predicate[predicates.size()]);
388 }
389
390 private static void addEqualsPredicate(CriteriaBuilder cb, Root<MCRUser> root,
391 SingularAttribute<MCRUser, String> attribute, String string, ArrayList<Predicate> predicates) {
392 if (isValidSearchPattern(string)) {
393 predicates.add(cb.equal(root.get(attribute), string));
394 }
395 }
396
397 private static void addSearchPredicate(CriteriaBuilder cb, Root<MCRUser> root,
398 SingularAttribute<MCRUser, String> attribute, String searchPattern, ArrayList<Predicate> predicates) {
399 if (isValidSearchPattern(searchPattern)) {
400 predicates.add(buildSearchPredicate(cb, root, attribute, searchPattern));
401 }
402 }
403
404 private static Predicate buildSearchPredicate(CriteriaBuilder cb, Root<MCRUser> root,
405 SingularAttribute<MCRUser, String> attribute, String searchPattern) {
406 return cb.like(cb.lower(root.get(attribute)), buildSearchPattern(searchPattern));
407 }
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423 @Deprecated
424 public static List<MCRUser> listUsers(String userPattern, String realm, String namePattern) {
425 return listUsers(userPattern, realm, namePattern, null);
426 }
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442 public static List<MCRUser> listUsers(String userPattern, String realm, String namePattern, String mailPattern) {
443 return listUsers(userPattern, realm, namePattern, mailPattern, null, 0, Integer.MAX_VALUE);
444 }
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463 public static List<MCRUser> listUsers(String userPattern, String realm, String namePattern, String mailPattern,
464 String attributeNamePattern, int offset, int limit) {
465 EntityManager em = MCREntityManagerProvider.getCurrentEntityManager();
466 CriteriaBuilder cb = em.getCriteriaBuilder();
467 CriteriaQuery<MCRUser> query = cb.createQuery(MCRUser.class);
468 Root<MCRUser> user = query.from(MCRUser.class);
469 return em
470 .createQuery(
471 query
472 .where(
473 buildCondition(cb, user, userPattern, realm, namePattern, mailPattern, attributeNamePattern)))
474 .setFirstResult(offset)
475 .setMaxResults(limit)
476 .getResultList();
477 }
478
479
480
481
482
483
484
485
486
487
488
489
490 @Deprecated
491 public static int countUsers(String userPattern, String realm, String namePattern) {
492 return countUsers(userPattern, realm, namePattern, null);
493 }
494
495
496
497
498
499
500
501
502
503
504
505
506 public static int countUsers(String userPattern, String realm, String namePattern, String mailPattern) {
507 return countUsers(userPattern, realm, namePattern, mailPattern, null);
508 }
509
510
511
512
513
514
515
516
517
518
519
520
521
522 public static int countUsers(String userPattern, String realm, String namePattern, String mailPattern,
523 String attributeNamePattern) {
524 EntityManager em = MCREntityManagerProvider.getCurrentEntityManager();
525 CriteriaBuilder cb = em.getCriteriaBuilder();
526 CriteriaQuery<Number> query = cb.createQuery(Number.class);
527 Root<MCRUser> user = query.from(MCRUser.class);
528 return em
529 .createQuery(
530 query
531 .select(cb.count(user))
532 .where(
533 buildCondition(cb, user, userPattern, realm, namePattern, mailPattern, attributeNamePattern)))
534 .getSingleResult().intValue();
535 }
536
537
538
539
540
541
542
543
544 public static MCRUser login(String userName, String password) {
545 return login(userName, password, Collections.emptyList());
546 }
547
548
549
550
551
552
553
554
555
556 public static MCRUser login(String userName, String password, List<String> allowedRoles) {
557 MCRUser user = checkPassword(userName, password);
558 if (user == null) {
559 return null;
560 }
561 if (!allowedRoles.isEmpty()) {
562 Collection<String> userRoles = user.getSystemRoleIDs();
563 LOGGER.info("Comparing user roles " + userRoles + " against list of allowed roles " + allowedRoles);
564 boolean hasAnyAllowedRole = userRoles.stream().anyMatch(allowedRoles::contains);
565 if (!hasAnyAllowedRole) {
566 return null;
567 }
568 }
569 user.setLastLogin();
570 updateUser(user);
571 MCRSessionMgr.getCurrentSession().setUserInformation(user);
572 return user;
573 }
574
575
576
577
578
579 public static MCRUser getCurrentUser() {
580 MCRUserInformation userInformation = MCRSessionMgr.getCurrentSession().getUserInformation();
581 if (userInformation instanceof MCRUser) {
582 return (MCRUser) userInformation;
583 } else {
584 return new MCRTransientUser(userInformation);
585 }
586 }
587
588
589
590
591
592
593
594
595
596
597
598 public static MCRUser checkPassword(String userName, String password) {
599 MCRUser user = getUser(userName);
600 if (user == null || user.getHashType() == null) {
601 LOGGER.warn(() -> "User not found: " + userName);
602 waitLoginPanalty();
603 return null;
604 }
605 if (password == null) {
606 LOGGER.warn("No password for user {} entered", userName);
607 waitLoginPanalty();
608 return null;
609 }
610 if (!user.loginAllowed()) {
611 if (user.isDisabled()) {
612 LOGGER.warn("User {} was disabled!", user.getUserID());
613 } else {
614 LOGGER.warn("Password expired for user {} on {}", user.getUserID(),
615 MCRXMLFunctions.getISODate(user.getValidUntil(), MCRISO8601Format.COMPLETE_HH_MM_SS.toString()));
616 }
617 return null;
618 }
619 try {
620 switch (user.getHashType()) {
621 case crypt:
622
623 String passwdHash = user.getPassword();
624 String salt = passwdHash.substring(0, 3);
625 if (!MCRUtils.asCryptString(salt, password).equals(passwdHash)) {
626
627 waitLoginPanalty();
628 return null;
629 }
630
631 updatePasswordHashToSHA256(user, password);
632 break;
633 case md5:
634 if (!MCRUtils.asMD5String(1, null, password).equals(user.getPassword())) {
635 waitLoginPanalty();
636 return null;
637 }
638
639 updatePasswordHashToSHA256(user, password);
640 break;
641 case sha1:
642 if (!MCRUtils.asSHA1String(HASH_ITERATIONS, Base64.getDecoder().decode(user.getSalt()), password)
643 .equals(user.getPassword())) {
644 waitLoginPanalty();
645 return null;
646 }
647
648 updatePasswordHashToSHA256(user, password);
649 break;
650 case sha256:
651 if (!MCRUtils.asSHA256String(HASH_ITERATIONS, Base64.getDecoder().decode(user.getSalt()), password)
652 .equals(user.getPassword())) {
653 waitLoginPanalty();
654 return null;
655 }
656 break;
657 default:
658 throw new MCRException("Cannot validate hash type " + user.getHashType());
659 }
660 } catch (NoSuchAlgorithmException e) {
661 throw new MCRException("Error while validating login", e);
662 }
663 return user;
664 }
665
666 private static void waitLoginPanalty() {
667 try {
668 Thread.sleep(3000);
669 } catch (InterruptedException e) {
670 }
671 }
672
673
674
675
676
677
678 public static void setPassword(MCRUser user, String password) {
679 MCRSession session = MCRSessionMgr.getCurrentSession();
680 MCRUserInformation currentUser = session.getUserInformation();
681 MCRUser myUser = getUser(user.getUserName(), user.getRealmID());
682 boolean allowed = MCRAccessManager.checkPermission(MCRUser2Constants.USER_ADMIN_PERMISSION)
683 || currentUser.equals(myUser.getOwner())
684 || (currentUser.equals(user) && myUser.hasNoOwner() || !myUser.isLocked());
685 if (!allowed) {
686 throw new MCRException("You are not allowed to change password of user: " + user);
687 }
688 updatePasswordHashToSHA256(myUser, password);
689 updateUser(myUser);
690 }
691
692 static void updatePasswordHashToSHA256(MCRUser user, String password) {
693 String newHash;
694 byte[] salt = generateSalt();
695 try {
696 newHash = MCRUtils.asSHA256String(HASH_ITERATIONS, salt, password);
697 } catch (Exception e) {
698 throw new MCRException("Could not update user password hash to SHA-256.", e);
699 }
700 user.setSalt(Base64.getEncoder().encodeToString(salt));
701 user.setHashType(MCRPasswordHashType.sha256);
702 user.setPassword(newHash);
703 }
704
705 private static byte[] generateSalt() {
706 return SECURE_RANDOM.generateSeed(8);
707 }
708
709 private static Optional<MCRUser> getByNaturalID(EntityManager em, String userName, String realmId) {
710 CriteriaBuilder cb = em.getCriteriaBuilder();
711 CriteriaQuery<MCRUser> query = cb.createQuery(MCRUser.class);
712 Root<MCRUser> users = query.from(MCRUser.class);
713 users.fetch(MCRUser_.owner.getName(), JoinType.LEFT);
714 try {
715 return Optional
716 .of(em
717 .createQuery(query
718 .distinct(true)
719 .where(getUserRealmCriterion(cb, users, userName, realmId)))
720 .getSingleResult());
721 } catch (NoResultException e) {
722 return Optional.empty();
723 }
724 }
725
726 private static Predicate[] getUserRealmCriterion(CriteriaBuilder cb, Root<MCRUser> root, String user,
727 String realmId) {
728 if (realmId == null) {
729 realmId = MCRRealmFactory.getLocalRealm().getID();
730 }
731 return new Predicate[] { cb.equal(root.get(MCRUser_.userName), user),
732 cb.equal(root.get(MCRUser_.realmID), realmId) };
733 }
734 }