package svanimpe.reminders.domain; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.logging.Level; import java.util.logging.Logger; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.SecondaryTable; import javax.persistence.Table; import javax.persistence.Transient; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; import svanimpe.reminders.validation.OnPasswordUpdate; import svanimpe.reminders.validation.ValidPassword; import svanimpe.reminders.validation.ValidUsername; import static svanimpe.reminders.util.Utilities.*; /* * Note that this class uses unnecessary mapping annotations to set table and column names. This is * done to make the mapping explicit and serves as a reminder because the names are needed to set up * the security realm. The table name is set to TBL_USER because USER is a reserved SQL keyword. * Passwords are stored in a separate table, for good form. */ @Entity @Table(name = "TBL_USER") @SecondaryTable(name = "USER_PASSWORD") @NamedQueries({ @NamedQuery(name = "User.findAll", query = "SELECT u FROM User u") }) public class User { @Id @ValidUsername private String username; public String getUsername() { return username; } public void setUsername(String username) { this.username = cleanUp(username); } private String fullName; public String getFullName() { return fullName; } public void setFullName(String fullName) { this.fullName = cleanUp(fullName); } public static final String DEFAULT_PROFILE_PICTURE = "default.png"; @NotNull(message = "profilePicture should never be null") private String profilePicture = DEFAULT_PROFILE_PICTURE; public String getProfilePicture() { return profilePicture; } public void setProfilePicture(String profilePicture) { this.profilePicture = cleanUp(profilePicture, DEFAULT_PROFILE_PICTURE); } /* * The plain text password is used for validation purposes only. It is not persisted. Only the * encrypted password is persisted. The plain text password also cannot be read, it can only be * set. The method getPassword returns the encrypted password. */ @Transient @ValidPassword(groups = OnPasswordUpdate.class) private String plainPassword; @NotNull(message = "USER_PASSWORD") @Pattern(regexp = "[A-Fa-f0-9]{64}+", message = "invalid encrypted password") @Column(name = "PASSWORD", table = "USER_PASSWORD") private String encryptedPassword; /* * Returns the encrypted password. Returns null as long as the password is not yet set. */ public String getPassword() { return encryptedPassword; } /* * Sets and encrypts the password. Will trim off any leading or trailing whitespace before * encryption. Null values are replaced with an empty string to avoid NullPointerExceptions. */ public void setPassword(String plainPassword) { this.plainPassword = cleanUp(plainPassword, ""); try { BigInteger hash = new BigInteger(1, MessageDigest.getInstance("SHA-256").digest(this.plainPassword.getBytes("UTF-8"))); encryptedPassword = hash.toString(16); } catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) { Logger.getLogger(User.class.getName()).log(Level.SEVERE, null, ex); } } @ElementCollection @Enumerated(EnumType.STRING) @CollectionTable(name = "USER_ROLES", joinColumns = @JoinColumn(name = "USERNAME")) @Column(name = "ROLES") private final List<Role> roles = new ArrayList<>(); public List<Role> getRoles() { return roles; } @Override public int hashCode() { int hash = 7; hash = 13 * hash + Objects.hashCode(this.username); return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final User other = (User) obj; return Objects.equals(this.username, other.username); } @Override public String toString() { return username; } }