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