View Javadoc
1   /*
2    * Copyright (C) 2023 B3Partners B.V.
3    *
4    * SPDX-License-Identifier: MIT
5    */
6   package org.tailormap.api.persistence;
7   
8   import com.fasterxml.jackson.annotation.JsonIgnore;
9   import com.fasterxml.jackson.annotation.JsonProperty;
10  import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
11  import jakarta.persistence.Column;
12  import jakarta.persistence.Entity;
13  import jakarta.persistence.EntityListeners;
14  import jakarta.persistence.Id;
15  import jakarta.persistence.JoinColumn;
16  import jakarta.persistence.JoinTable;
17  import jakarta.persistence.ManyToMany;
18  import jakarta.persistence.Table;
19  import jakarta.persistence.Version;
20  import jakarta.validation.constraints.Email;
21  import jakarta.validation.constraints.NotNull;
22  import jakarta.validation.constraints.Pattern;
23  import jakarta.validation.constraints.Size;
24  import java.time.ZoneId;
25  import java.time.ZonedDateTime;
26  import java.util.ArrayList;
27  import java.util.HashSet;
28  import java.util.List;
29  import java.util.Set;
30  import org.hibernate.annotations.Type;
31  import org.hibernate.envers.Audited;
32  import org.tailormap.api.persistence.helper.AdminAdditionalPropertyHelper;
33  import org.tailormap.api.persistence.json.AdminAdditionalProperty;
34  import org.tailormap.api.persistence.listener.EntityEventPublisher;
35  import org.tailormap.api.util.Constants;
36  import org.tailormap.api.util.TMPasswordDeserializer;
37  
38  @Audited
39  @Entity
40  @Table(name = "users")
41  @EntityListeners(EntityEventPublisher.class)
42  public class User {
43  
44    @Id
45    @Pattern(regexp = Constants.NAME_REGEX, message = "User" + Constants.NAME_REGEX_INVALID_MESSAGE) private String username;
46  
47    @Version
48    private Long version;
49  
50    @NotNull @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
51    @JsonDeserialize(using = TMPasswordDeserializer.class)
52    // bcrypt MAX/MIN length is 60 + {bcrypt} token, but for testing we use shorter plain text
53    // passwords
54    @Size(max = (8 + 60))
55    private String password;
56  
57    @Email private String email;
58  
59    private String name;
60  
61    private String organisation;
62  
63    @Column(columnDefinition = "text")
64    private String notes;
65  
66    @Column(columnDefinition = "timestamp with time zone")
67    private ZonedDateTime validUntil;
68  
69    private boolean enabled = true;
70  
71    @Type(value = io.hypersistence.utils.hibernate.type.json.JsonBinaryType.class)
72    @Column(columnDefinition = "jsonb")
73    private List<AdminAdditionalProperty> additionalProperties = new ArrayList<>();
74  
75    @ManyToMany
76    @JoinTable(
77        name = "user_groups",
78        joinColumns = @JoinColumn(name = "username"),
79        inverseJoinColumns = @JoinColumn(name = "group_name"))
80    private Set<Group> groups = new HashSet<>();
81  
82    public String getUsername() {
83      return username;
84    }
85  
86    public User setUsername(String username) {
87      this.username = username;
88      return this;
89    }
90  
91    public Long getVersion() {
92      return version;
93    }
94  
95    public User setVersion(Long version) {
96      this.version = version;
97      return this;
98    }
99  
100   public String getPassword() {
101     return password;
102   }
103 
104   public User setPassword(String passwordHash) {
105     this.password = passwordHash;
106     return this;
107   }
108 
109   public String getEmail() {
110     return email;
111   }
112 
113   public User setEmail(String email) {
114     this.email = email;
115     return this;
116   }
117 
118   public String getName() {
119     return name;
120   }
121 
122   public User setName(String name) {
123     this.name = name;
124     return this;
125   }
126 
127   public String getOrganisation() {
128     return organisation;
129   }
130 
131   public User setOrganisation(String organisation) {
132     this.organisation = organisation;
133     return this;
134   }
135 
136   public String getNotes() {
137     return notes;
138   }
139 
140   public User setNotes(String notes) {
141     this.notes = notes;
142     return this;
143   }
144 
145   public List<AdminAdditionalProperty> getAdditionalProperties() {
146     return additionalProperties;
147   }
148 
149   public User setAdditionalProperties(List<AdminAdditionalProperty> additionalProperties) {
150     this.additionalProperties = additionalProperties;
151     return this;
152   }
153 
154   public Set<Group> getGroups() {
155     return groups;
156   }
157 
158   public User setGroups(Set<Group> groups) {
159     this.groups = groups;
160     return this;
161   }
162 
163   public Set<String> getGroupNames() {
164     return groups.stream().map(Group::getName).collect(java.util.stream.Collectors.toSet());
165   }
166 
167   public ZonedDateTime getValidUntil() {
168     return validUntil;
169   }
170 
171   public User setValidUntil(ZonedDateTime validUntil) {
172     this.validUntil = validUntil;
173     return this;
174   }
175 
176   public boolean isEnabled() {
177     return enabled;
178   }
179 
180   public User setEnabled(boolean enabled) {
181     this.enabled = enabled;
182     return this;
183   }
184 
185   public void addOrUpdateAdminProperty(String key, Object value, boolean isPublic) {
186     AdminAdditionalPropertyHelper.addOrUpdateAdminProperty(additionalProperties, key, value, isPublic);
187   }
188 
189   /**
190    * Check if the user is enabled and the validUntil date has not passed yet (or null).
191    *
192    * @return true if the user is enabled and valid, false otherwise.
193    */
194   @JsonIgnore
195   public boolean isEnabledAndValidUntil() {
196     return enabled && (validUntil == null || validUntil.isAfter(ZonedDateTime.now(ZoneId.systemDefault())));
197   }
198 }