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