User.java
/*
* Copyright (C) 2023 B3Partners B.V.
*
* SPDX-License-Identifier: MIT
*/
package org.tailormap.api.persistence;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Table;
import jakarta.persistence.Version;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.envers.Audited;
import org.hibernate.type.SqlTypes;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import org.tailormap.api.persistence.helper.AdminAdditionalPropertyHelper;
import org.tailormap.api.persistence.json.AdminAdditionalProperty;
import org.tailormap.api.persistence.listener.EntityEventPublisher;
import org.tailormap.api.util.Constants;
import org.tailormap.api.util.TMPasswordDeserializer;
@Audited
@Entity
@Table(name = "users")
@EntityListeners({EntityEventPublisher.class, AuditingEntityListener.class})
public class User extends AuditMetadata {
@Id
@Pattern(regexp = Constants.NAME_REGEX, message = "User" + Constants.NAME_REGEX_INVALID_MESSAGE) private String username;
@Version
private Long version;
@NotNull @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@JsonDeserialize(using = TMPasswordDeserializer.class)
// bcrypt MAX/MIN length is 60 + {bcrypt} token, but for testing we use shorter plain text
// passwords
@Size(max = (8 + 60))
private String password;
@Email private String email;
private String name;
private String organisation;
@Column(columnDefinition = "text")
private String notes;
@Column(columnDefinition = "timestamp with time zone")
private ZonedDateTime validUntil;
private boolean enabled = true;
@JdbcTypeCode(SqlTypes.JSON)
@Column(columnDefinition = "jsonb")
private List<AdminAdditionalProperty> additionalProperties = new ArrayList<>();
@ManyToMany
@JoinTable(
name = "user_groups",
joinColumns = @JoinColumn(name = "username"),
inverseJoinColumns = @JoinColumn(name = "group_name"))
private Set<Group> groups = new HashSet<>();
public String getUsername() {
return username;
}
public User setUsername(String username) {
this.username = username;
return this;
}
public Long getVersion() {
return version;
}
public User setVersion(Long version) {
this.version = version;
return this;
}
public String getPassword() {
return password;
}
public User setPassword(String passwordHash) {
this.password = passwordHash;
return this;
}
public String getEmail() {
return email;
}
public User setEmail(String email) {
this.email = email;
return this;
}
public String getName() {
return name;
}
public User setName(String name) {
this.name = name;
return this;
}
public String getOrganisation() {
return organisation;
}
public User setOrganisation(String organisation) {
this.organisation = organisation;
return this;
}
public String getNotes() {
return notes;
}
public User setNotes(String notes) {
this.notes = notes;
return this;
}
public List<AdminAdditionalProperty> getAdditionalProperties() {
return additionalProperties;
}
public User setAdditionalProperties(List<AdminAdditionalProperty> additionalProperties) {
this.additionalProperties = additionalProperties;
return this;
}
public Set<Group> getGroups() {
return groups;
}
public User setGroups(Set<Group> groups) {
this.groups = groups;
return this;
}
public Set<String> getGroupNames() {
return groups.stream().map(Group::getName).collect(java.util.stream.Collectors.toSet());
}
public ZonedDateTime getValidUntil() {
return validUntil;
}
public User setValidUntil(ZonedDateTime validUntil) {
this.validUntil = validUntil;
return this;
}
public boolean isEnabled() {
return enabled;
}
public User setEnabled(boolean enabled) {
this.enabled = enabled;
return this;
}
public void addOrUpdateAdminProperty(String key, Object value, boolean isPublic) {
AdminAdditionalPropertyHelper.addOrUpdateAdminProperty(additionalProperties, key, value, isPublic);
}
/**
* Check if the user is enabled and the validUntil date has not passed yet (or null).
*
* @return true if the user is enabled and valid, false otherwise.
*/
@JsonIgnore
public boolean isEnabledAndValidUntil() {
return enabled && (validUntil == null || validUntil.isAfter(ZonedDateTime.now(ZoneId.systemDefault())));
}
}