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.io.Serializable;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Collection;
25 import java.util.Date;
26 import java.util.HashSet;
27 import java.util.Objects;
28 import java.util.Optional;
29 import java.util.Set;
30 import java.util.SortedSet;
31 import java.util.TreeSet;
32 import java.util.stream.Collectors;
33 import java.util.stream.Stream;
34
35 import org.hibernate.annotations.SortNatural;
36 import org.mycore.common.MCRException;
37 import org.mycore.common.MCRUserInformation;
38 import org.mycore.user2.annotation.MCRUserAttributeJavaConverter;
39 import org.mycore.user2.utils.MCRRolesConverter;
40 import org.mycore.user2.utils.MCRUserNameConverter;
41
42 import jakarta.persistence.Access;
43 import jakarta.persistence.AccessType;
44 import jakarta.persistence.CollectionTable;
45 import jakarta.persistence.Column;
46 import jakarta.persistence.ElementCollection;
47 import jakarta.persistence.Entity;
48 import jakarta.persistence.EnumType;
49 import jakarta.persistence.Enumerated;
50 import jakarta.persistence.FetchType;
51 import jakarta.persistence.GeneratedValue;
52 import jakarta.persistence.GenerationType;
53 import jakarta.persistence.Id;
54 import jakarta.persistence.Index;
55 import jakarta.persistence.JoinColumn;
56 import jakarta.persistence.ManyToOne;
57 import jakarta.persistence.NamedQueries;
58 import jakarta.persistence.NamedQuery;
59 import jakarta.persistence.Table;
60 import jakarta.persistence.Transient;
61 import jakarta.persistence.UniqueConstraint;
62 import jakarta.xml.bind.annotation.XmlAccessType;
63 import jakarta.xml.bind.annotation.XmlAccessorType;
64 import jakarta.xml.bind.annotation.XmlAttribute;
65 import jakarta.xml.bind.annotation.XmlElement;
66 import jakarta.xml.bind.annotation.XmlElementWrapper;
67 import jakarta.xml.bind.annotation.XmlRootElement;
68 import jakarta.xml.bind.annotation.XmlType;
69
70
71
72
73
74
75
76
77
78
79
80 @Entity
81 @Access(AccessType.PROPERTY)
82 @Table(name = "MCRUser", uniqueConstraints = @UniqueConstraint(columnNames = { "userName", "realmID" }))
83 @NamedQueries(@NamedQuery(name = "MCRUser.byPropertyValue",
84 query = "SELECT u FROM MCRUser u JOIN FETCH u.attributes ua WHERE ua.name = :name AND ua.value = :value"))
85 @XmlRootElement(name = "user")
86 @XmlAccessorType(XmlAccessType.NONE)
87 @XmlType(propOrder = { "ownerId", "realName", "eMail", "lastLogin", "validUntil", "roles", "attributes",
88 "password" })
89 public class MCRUser implements MCRUserInformation, Cloneable, Serializable {
90 private static final long serialVersionUID = 3378645055646901800L;
91
92
93 int internalID;
94
95
96 @XmlAttribute(name = "locked")
97 private boolean locked;
98
99 @XmlAttribute(name = "disabled")
100 private boolean disabled;
101
102
103 @org.mycore.user2.annotation.MCRUserAttribute
104 @MCRUserAttributeJavaConverter(MCRUserNameConverter.class)
105 @XmlAttribute(name = "name")
106 private String userName;
107
108 @XmlElement
109 private Password password;
110
111
112 @XmlAttribute(name = "realm")
113 private String realmID;
114
115
116 private MCRUser owner;
117
118
119 @org.mycore.user2.annotation.MCRUserAttribute
120 @XmlElement
121 private String realName;
122
123
124 @org.mycore.user2.annotation.MCRUserAttribute
125 @XmlElement
126 private String eMail;
127
128
129 @XmlElement
130 private Date lastLogin;
131
132 @XmlElement
133 private Date validUntil;
134
135 private SortedSet<MCRUserAttribute> attributes;
136
137 @Transient
138 private Collection<String> systemRoles;
139
140 @Transient
141 private Collection<String> externalRoles;
142
143 protected MCRUser() {
144 this(null);
145 }
146
147
148
149
150
151
152
153 public MCRUser(String userName, MCRRealm mcrRealm) {
154 this(userName, mcrRealm.getID());
155 }
156
157
158
159
160
161
162
163 public MCRUser(String userName, String realmID) {
164 this.userName = userName;
165 this.realmID = realmID;
166 this.systemRoles = new HashSet<>();
167 this.externalRoles = new HashSet<>();
168 this.attributes = new TreeSet<>();
169 this.password = new Password();
170 }
171
172
173
174
175
176
177 public MCRUser(String userName) {
178 this(userName, MCRRealmFactory.getLocalRealm().getID());
179 }
180
181
182
183
184 @Id
185 @GeneratedValue(strategy = GenerationType.IDENTITY)
186 @Column
187 int getInternalID() {
188 return internalID;
189 }
190
191
192
193
194 void setInternalID(int internalID) {
195 this.internalID = internalID;
196 }
197
198 @Column(name = "locked", nullable = true)
199 public boolean isLocked() {
200 return locked;
201 }
202
203 public void setLocked(Boolean locked) {
204 this.locked = locked == null ? false : locked;
205 }
206
207
208
209
210 @Column(name = "disabled", nullable = true)
211 public boolean isDisabled() {
212 return disabled;
213 }
214
215
216
217
218 public void setDisabled(Boolean disabled) {
219 this.disabled = disabled == null ? false : disabled;
220 }
221
222
223
224
225
226
227
228 @Column(name = "userName", nullable = false)
229 public String getUserName() {
230 return userName;
231 }
232
233
234
235
236
237
238
239
240 void setUserName(String userName) {
241 this.userName = userName;
242 }
243
244
245
246
247
248
249 @Transient
250 public MCRRealm getRealm() {
251 return MCRRealmFactory.getRealm(realmID);
252 }
253
254
255
256
257
258
259
260
261 void setRealm(MCRRealm realm) {
262 this.realmID = realm.getID();
263 }
264
265
266
267
268
269
270 @Column(name = "realmID", length = 128, nullable = false)
271 public String getRealmID() {
272 return realmID;
273 }
274
275
276
277
278
279
280
281
282 void setRealmID(String realmID) {
283 if (realmID == null) {
284 setRealm(MCRRealmFactory.getLocalRealm());
285 } else {
286 setRealm(MCRRealmFactory.getRealm(realmID));
287 }
288 }
289
290
291
292
293 @Column(name = "password", nullable = true)
294 public String getPassword() {
295 return password == null ? null : password.hash;
296 }
297
298
299
300
301 public void setPassword(String password) {
302 this.password.hash = password;
303 }
304
305
306
307
308 @Column(name = "salt", nullable = true)
309 public String getSalt() {
310 return password == null ? null : password.salt;
311 }
312
313
314
315
316 public void setSalt(String salt) {
317 this.password.salt = salt;
318 }
319
320
321
322
323 @Column(name = "hashType", nullable = true)
324 @Enumerated(EnumType.STRING)
325 public MCRPasswordHashType getHashType() {
326 return password == null ? null : password.hashType;
327 }
328
329
330
331
332 public void setHashType(MCRPasswordHashType hashType) {
333 this.password.hashType = hashType;
334 }
335
336
337
338
339
340
341
342 @ManyToOne
343 @JoinColumn(name = "owner", nullable = true)
344 public MCRUser getOwner() {
345 return owner;
346 }
347
348
349
350
351
352
353
354 public void setOwner(MCRUser owner) {
355 this.owner = owner;
356 }
357
358
359
360
361
362
363
364
365
366 public boolean hasNoOwner() {
367 return owner == null;
368 }
369
370
371
372
373
374
375 @Column(name = "realName", nullable = true)
376 public String getRealName() {
377 return realName;
378 }
379
380
381
382
383
384
385 public void setRealName(String realName) {
386 this.realName = realName;
387 }
388
389
390
391
392
393
394 @Transient
395 public String getEMailAddress() {
396 return eMail;
397 }
398
399 @Column(name = "eMail", nullable = true)
400 private String getEMail() {
401 return eMail;
402 }
403
404
405
406
407
408
409 public void setEMail(String eMail) {
410 this.eMail = eMail;
411 }
412
413
414
415
416
417
418 @Column(name = "hint", nullable = true)
419 public String getHint() {
420 return password == null ? null : password.hint;
421 }
422
423
424
425
426
427
428 public void setHint(String hint) {
429 this.password.hint = hint;
430 }
431
432
433
434
435
436
437 @Column(name = "lastLogin", nullable = true)
438 public Date getLastLogin() {
439 if (lastLogin == null) {
440 return null;
441 }
442 return new Date(lastLogin.getTime());
443 }
444
445
446
447
448
449
450 public void setLastLogin(Date lastLogin) {
451 this.lastLogin = lastLogin == null ? null : new Date(lastLogin.getTime());
452 }
453
454
455
456
457 public void setLastLogin() {
458 this.lastLogin = new Date();
459 }
460
461
462
463
464 @Override
465 public boolean equals(Object obj) {
466 if (this == obj) {
467 return true;
468 }
469 if (obj == null) {
470 return false;
471 }
472 if (!(obj instanceof MCRUser)) {
473 return false;
474 }
475 MCRUser other = (MCRUser) obj;
476 if (realmID == null) {
477 if (other.realmID != null) {
478 return false;
479 }
480 } else if (!realmID.equals(other.realmID)) {
481 return false;
482 }
483 if (userName == null) {
484 return other.userName == null;
485 } else {
486 return userName.equals(other.userName);
487 }
488 }
489
490
491
492
493 @Override
494 public int hashCode() {
495 final int prime = 31;
496 int result = 1;
497 result = prime * result + ((realmID == null) ? 0 : realmID.hashCode());
498 result = prime * result + ((userName == null) ? 0 : userName.hashCode());
499 return result;
500 }
501
502 @Transient
503 @Override
504 public String getUserID() {
505 String cuid = this.getUserName();
506 if (!getRealm().equals(MCRRealmFactory.getLocalRealm())) {
507 cuid += "@" + getRealmID();
508 }
509
510 return cuid;
511 }
512
513
514
515
516
517
518 @Override
519 public String getUserAttribute(String attribute) {
520 switch (attribute) {
521 case MCRUserInformation.ATT_REAL_NAME:
522 return getRealName();
523 case MCRUserInformation.ATT_EMAIL:
524 return getEMailAddress();
525 default:
526 Set<MCRUserAttribute> attrs = attributes.stream()
527 .filter(a -> a.getName().equals(attribute))
528 .collect(Collectors.toSet());
529 if (attrs.size() > 1) {
530 throw new MCRException(getUserID() + ": user attribute " + attribute + " is not unique");
531 }
532 return attrs.stream()
533 .map(MCRUserAttribute::getValue)
534 .findAny().orElse(null);
535 }
536 }
537
538 @Override
539 public boolean isUserInRole(final String role) {
540 boolean directMember = getSystemRoleIDs().contains(role) || getExternalRoleIDs().contains(role);
541 if (directMember) {
542 return true;
543 }
544 return MCRRoleManager.isAssignedToRole(this, role);
545 }
546
547
548
549
550 public void setAttributes(SortedSet<MCRUserAttribute> attributes) {
551 this.attributes = attributes;
552 }
553
554 @ElementCollection(fetch = FetchType.EAGER)
555 @CollectionTable(name = "MCRUserAttr",
556 joinColumns = @JoinColumn(name = "id"),
557 indexes = { @Index(name = "MCRUserAttributes", columnList = "name, value"),
558 @Index(name = "MCRUserValues", columnList = "value") })
559 @SortNatural
560 @XmlElementWrapper(name = "attributes")
561 @XmlElement(name = "attribute")
562 public SortedSet<MCRUserAttribute> getAttributes() {
563 return this.attributes;
564 }
565
566
567
568
569
570 @Transient
571 public Collection<String> getSystemRoleIDs() {
572 return systemRoles;
573 }
574
575
576
577
578
579 @Transient
580 public Collection<String> getExternalRoleIDs() {
581 return externalRoles;
582 }
583
584
585
586
587
588 public void assignRole(String roleName) {
589 MCRRole mcrRole = MCRRoleManager.getRole(roleName);
590 if (mcrRole == null) {
591 throw new MCRException("Could not find role " + roleName);
592 }
593 assignRole(mcrRole);
594 }
595
596 private void assignRole(MCRRole mcrRole) {
597 if (mcrRole.isSystemRole()) {
598 getSystemRoleIDs().add(mcrRole.getName());
599 } else {
600 getExternalRoleIDs().add(mcrRole.getName());
601 }
602 }
603
604
605
606
607
608 public void unassignRole(String roleName) {
609 MCRRole mcrRole = MCRRoleManager.getRole(roleName);
610 if (mcrRole == null) {
611 throw new MCRException("Could not find role " + roleName);
612 }
613 if (mcrRole.isSystemRole()) {
614 getSystemRoleIDs().remove(mcrRole.getName());
615 } else {
616 getExternalRoleIDs().remove(mcrRole.getName());
617 }
618 }
619
620
621
622
623 public void enableLogin() {
624 setDisabled(false);
625 }
626
627
628
629
630 public void disableLogin() {
631 setDisabled(true);
632 }
633
634
635
636
637 public boolean loginAllowed() {
638 return !disabled && (validUntil == null || validUntil.after(new Date()));
639 }
640
641
642
643
644 @Column(name = "validUntil", nullable = true)
645 public Date getValidUntil() {
646 if (validUntil == null) {
647 return null;
648 }
649 return new Date(validUntil.getTime());
650 }
651
652
653
654
655
656 public void setValidUntil(Date validUntil) {
657 this.validUntil = validUntil == null ? null : new Date(validUntil.getTime());
658 }
659
660
661
662 @Transient
663 Collection<String> getRolesCollection() {
664 return Arrays.stream(getRoles()).map(MCRRole::getName).collect(Collectors.toSet());
665 }
666
667 @org.mycore.user2.annotation.MCRUserAttribute(name = "roles", separator = ";")
668 @MCRUserAttributeJavaConverter(MCRRolesConverter.class)
669 void setRolesCollection(Collection<String> roles) {
670 for (String role : roles) {
671 assignRole(role);
672 }
673 }
674
675
676
677 @Transient
678 @XmlElementWrapper(name = "roles")
679 @XmlElement(name = "role")
680 private MCRRole[] getRoles() {
681 if (getSystemRoleIDs().isEmpty() && getExternalRoleIDs().isEmpty()) {
682 return null;
683 }
684 ArrayList<String> roleIds = new ArrayList<>(getSystemRoleIDs().size() + getExternalRoleIDs().size());
685 Collection<MCRRole> roles = new ArrayList<>(roleIds.size());
686 roleIds.addAll(getSystemRoleIDs());
687 roleIds.addAll(getExternalRoleIDs());
688 for (String roleName : roleIds) {
689 MCRRole role = MCRRoleManager.getRole(roleName);
690 if (role == null) {
691 throw new MCRException("Could not load role: " + roleName);
692 }
693 roles.add(role);
694 }
695 return roles.toArray(new MCRRole[roles.size()]);
696 }
697
698 @SuppressWarnings("unused")
699 private void setRoles(MCRRole[] roles) {
700 Stream.of(roles)
701 .map(MCRRole::getName)
702 .map(roleName -> {
703
704 MCRRole role = MCRRoleManager.getRole(roleName);
705 if (role == null) {
706 throw new MCRException("Could not load role: " + roleName);
707 }
708 return role;
709 })
710 .forEach(this::assignRole);
711 }
712
713 public void setUserAttribute(String name, String value) {
714 Optional<MCRUserAttribute> anyMatch = getAttributes().stream()
715 .filter(a -> a.getName().equals(Objects.requireNonNull(name)))
716 .findAny();
717 if (anyMatch.isPresent()) {
718 MCRUserAttribute attr = anyMatch.get();
719 attr.setValue(value);
720 getAttributes().removeIf(a -> a.getName().equals(name) && a != attr);
721 } else {
722 getAttributes().add(new MCRUserAttribute(name, value));
723 }
724 }
725
726 @Transient
727 @XmlElement(name = "owner")
728 private UserIdentifier getOwnerId() {
729 if (owner == null) {
730 return null;
731 }
732 UserIdentifier userIdentifier = new UserIdentifier();
733 userIdentifier.name = owner.getUserName();
734 userIdentifier.realm = owner.getRealmID();
735 return userIdentifier;
736 }
737
738 @SuppressWarnings("unused")
739 private void setOwnerId(UserIdentifier userIdentifier) {
740 if (userIdentifier.name.equals(this.userName) && userIdentifier.realm.equals(this.realmID)) {
741 setOwner(this);
742 return;
743 }
744 MCRUser owner = MCRUserManager.getUser(userIdentifier.name, userIdentifier.realm);
745 setOwner(owner);
746 }
747
748
749
750
751 @Override
752 public MCRUser clone() {
753 MCRUser copy = getSafeCopy();
754 if (copy.password == null) {
755 copy.password = new Password();
756 }
757 copy.password.hashType = this.password.hashType;
758 copy.password.hash = this.password.hash;
759 copy.password.salt = this.password.salt;
760 return copy;
761 }
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777 @Transient
778 public MCRUser getBasicCopy() {
779 MCRUser copy = new MCRUser(userName, realmID);
780 copy.locked = locked;
781 copy.disabled = disabled;
782 copy.owner = this.equals(this.owner) ? copy : this.owner;
783 copy.setAttributes(null);
784 copy.password = null;
785 return copy;
786 }
787
788
789
790
791
792
793
794
795
796
797
798 @Transient
799 public MCRUser getSafeCopy() {
800 MCRUser copy = getBasicCopy();
801 if (getHint() != null) {
802 copy.password = new Password();
803 copy.password.hint = getHint();
804 }
805 copy.setAttributes(new TreeSet<>());
806 copy.eMail = this.eMail;
807 copy.lastLogin = this.lastLogin;
808 copy.validUntil = this.validUntil;
809 copy.realName = this.realName;
810 copy.systemRoles.addAll(this.systemRoles);
811 copy.externalRoles.addAll(this.externalRoles);
812 copy.attributes.addAll(this.attributes);
813 return copy;
814 }
815
816 private static class Password implements Serializable {
817
818 private static final long serialVersionUID = 8068063832119405080L;
819
820 @XmlAttribute
821 private String hash;
822
823
824 @XmlAttribute
825 private String salt;
826
827 @XmlAttribute
828 private MCRPasswordHashType hashType;
829
830
831 @XmlAttribute
832 private String hint;
833
834 }
835
836 private static class UserIdentifier implements Serializable {
837
838 private static final long serialVersionUID = 4654103884660408929L;
839
840 @XmlAttribute
841 public String name;
842
843 @XmlAttribute
844 public String realm;
845 }
846 }