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