/* * Data Hub Service (DHuS) - For Space data distribution. * Copyright (C) 2013,2014,2015,2016 GAEL Systems * * This file is part of DHuS software sources. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package fr.gael.dhus.database.object; import java.io.Serializable; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Random; import java.util.Set; import java.util.UUID; 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.FetchType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.OneToMany; import javax.persistence.OneToOne; import javax.persistence.Table; import javax.persistence.Transient; import org.hibernate.annotations.Cascade; import org.hibernate.annotations.CascadeType; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.codec.Hex; import fr.gael.dhus.database.object.restriction.AccessRestriction; import fr.gael.dhus.service.exception.UserBadEncryptionException; @Entity @Table (name = "USERS") public class User extends AbstractTimestampEntity implements Serializable, UserDetails { private static final long serialVersionUID = -5230880505052446856L; private static final Random RANDOM = new Random (); private static final char[] SYMBOLS = new char[73]; /** * Generating password. */ static { for (int idx = 0; idx < 10; ++idx) SYMBOLS[idx] = (char) ('0' + idx); for (int idx = 10; idx < 36; ++idx) SYMBOLS[idx] = (char) ('a' + (idx - 10)); for (int idx = 36; idx < 62; ++idx) SYMBOLS[idx] = (char) ('A' + (idx - 36)); SYMBOLS[62] = '@'; SYMBOLS[63] = '/'; SYMBOLS[64] = '?'; SYMBOLS[65] = '$'; SYMBOLS[66] = '%'; SYMBOLS[67] = '?'; SYMBOLS[68] = '!'; SYMBOLS[69] = '.'; SYMBOLS[70] = ','; SYMBOLS[71] = ';'; SYMBOLS[72] = ':'; } public enum PasswordEncryption { NONE ("none"), MD5 ("MD5"), SHA1 ("SHA-1"), SHA256 ("SHA-256"), SHA384 ("SHA-384"), SHA512 ("SHA-512"); private String algorithmKey; private PasswordEncryption (String algorithm) { algorithmKey = algorithm; } public String getAlgorithmKey () { return algorithmKey; } public static PasswordEncryption fromAlgorithm (String hash) { for (PasswordEncryption pe:values()) if (pe.getAlgorithmKey().equals(hash)) return pe; throw new IllegalArgumentException("Unknown \""+hash+"\" algorithm."); } } @Id @Column (name = "UUID", nullable = false) private String uuid = UUID.randomUUID ().toString (); @Column (name = "LOGIN", unique = true) private String username; @Column (name = "PASSWORD", nullable = false, length = 128) private String password; @Transient private String tmpPassword; @Column (name = "PASSWORD_ENCRYPTION", nullable = false) @Enumerated (EnumType.STRING) private PasswordEncryption passwordEncryption = PasswordEncryption.NONE; @Column (name = "FIRSTNAME", nullable = true) private String firstname; @Column (name = "LASTNAME", nullable = true) private String lastname; @Column (name = "EMAIL", nullable = true) private String email; @Column (name = "PHONE", nullable = true) private String phone; @Column (name = "ADDRESS", nullable = true) private String address; @Column (name = "DELETED", columnDefinition = "BOOLEAN", nullable = false) private boolean deleted = false; @ElementCollection (targetClass = Role.class, fetch = FetchType.EAGER) @CollectionTable (name = "USER_ROLES", joinColumns = @JoinColumn (name = "USER_UUID")) @Enumerated (EnumType.STRING) @Cascade ({CascadeType.DELETE}) private List<Role> roles; /** * Setup the users preferences */ @OneToOne (fetch=FetchType.EAGER) @Cascade ({CascadeType.SAVE_UPDATE, CascadeType.DELETE}) private Preference preferences=new Preference (); /** * User's restrictions */ @OneToMany (fetch = FetchType.EAGER) @JoinTable (name = "USER_RESTRICTIONS", joinColumns = { @JoinColumn (name = "USER_UUID") }, inverseJoinColumns = { @JoinColumn (name = "RESTRICTION_UUID") }) @Cascade ({CascadeType.SAVE_UPDATE, CascadeType.DELETE}) private Set<AccessRestriction> restrictions; @Column (name = "COUNTRY", nullable = false, columnDefinition = "VARCHAR(255) default 'unknown'") private String country = "unknown"; @Column (name = "DOMAIN", nullable = false, columnDefinition = "VARCHAR(255) default 'unknown'") private String domain = "unknown"; @Column (name = "SUBDOMAIN", nullable = false, columnDefinition = "VARCHAR(255) default 'unknown'") private String subDomain = "unknown"; @Column (name = "USAGE", nullable = false, columnDefinition = "VARCHAR(255) default 'unknown'") private String usage = "unknown"; @Column (name = "SUBUSAGE", nullable = false, columnDefinition = "VARCHAR(255) default 'unknown'") private String subUsage = "unknown"; public String getUsername () { return username; } public String getPassword () { return password; } public void setUsername (String username) { if (username != null) this.username = username.toLowerCase (); } public void setEncryptedPassword (String password, PasswordEncryption enc) { this.password=password; setPasswordEncryption(enc); } public void setPassword (String password) { // Encrypt password with MessageDigest PasswordEncryption encryption = PasswordEncryption.MD5; setPasswordEncryption (encryption); if (encryption != PasswordEncryption.NONE) // when configurable { try { MessageDigest md = MessageDigest.getInstance (encryption.getAlgorithmKey ()); password = new String ( Hex.encode (md.digest (password.getBytes ("UTF-8")))); } catch (Exception e) { throw new UserBadEncryptionException ( "There was an error while encrypting password of user " + getUsername (), e); } } this.password = password; } public void setRoles (List<Role> roles) { this.roles = roles; } public List<Role> getRoles () { if (roles != null) return new ArrayList<Role> (roles); else return new ArrayList<Role> (); } public void addRole (Role role) { if (roles == null) { roles = new ArrayList<Role> (); } roles.add (role); } public void removeRole (Role role) { if (roles != null && roles.contains (role)) { roles.remove (role); } } public PasswordEncryption getPasswordEncryption () { return passwordEncryption; } /** * used internal * * @param password_encryption */ private void setPasswordEncryption (PasswordEncryption password_encryption) { this.passwordEncryption = password_encryption; } public String getFirstname () { return firstname; } public void setFirstname (String firstname) { this.firstname = firstname; } public String getLastname () { return lastname; } public void setLastname (String lastname) { this.lastname = lastname; } public String getEmail () { return email; } public void setEmail (String email) { this.email = email; } @Override public boolean isAccountNonExpired () { return true; } @Override public boolean isAccountNonLocked () { return true; } @Override public boolean isCredentialsNonExpired () { return true; } @Override public boolean isEnabled () { return true; } @Override public Collection<GrantedAuthority> getAuthorities () { ArrayList<GrantedAuthority> authorities = new ArrayList<GrantedAuthority> (); for (Role role : roles) { authorities.add (new SimpleGrantedAuthority(role.getAuthority ())); } return authorities; } /** * @param phone the phone to set */ public void setPhone (String phone) { this.phone = phone; } /** * @return the phone */ public String getPhone () { return phone; } /** * @param address the address to set */ public void setAddress (String address) { this.address = address; } /** * @return the address */ public String getAddress () { return address; } /** * @return the uuid */ public String getUUID () { return uuid; } /** * @param uuid the uuid to set */ public void setUUID (String uuid) { this.uuid = uuid; } /** * @param preferences the preferences to set */ public void setPreferences (Preference preferences) { this.preferences = preferences; } /** * @return the preferences */ public Preference getPreferences () { return preferences; } /** * @param deleted the deleted to set */ public void setDeleted (boolean deleted) { this.deleted = deleted; } /** * @return the deleted */ public boolean isDeleted () { return deleted; } public void addRestriction (AccessRestriction restriction) { if (restrictions == null) { restrictions = new HashSet<AccessRestriction> (); } restrictions.add (restriction); } public void setRestrictions (Set<AccessRestriction> restrictions) { this.restrictions = restrictions; } public Set<AccessRestriction> getRestrictions () { return restrictions; } @Override public String toString () { String str = new String ("Login name : " + getUsername () + "\n" + (tmpPassword != null ? checkedStr ("Password : ", tmpPassword, "\n") : "") + checkedStr ("Firstname : ", getFirstname (), "\n") + checkedStr ("Lastname : ", getLastname (), "\n") + checkedStr ("E-mail : ", getEmail (), "\n") + checkedStr ("Domain : ", getDisplayableDomain (), "\n") + checkedStr ("Usage : ", getDisplayableUsage (), "\n") + checkedStr ("Country : ", getCountry (), "\n") + checkedStr ("Phone : ", getPhone (), "\n") + checkedStr ("Address : ", getAddress (), "\n\n") + getServicesAsString (getRoles ()) + "\n" + getRestrictionsAsString (getRestrictions ())); return str; } private String getServicesAsString (List<Role> roles) { String str = "Available Services : "; if (roles == null || roles.isEmpty ()) return "Currently you have no available services." + " You have to wait that an administrator give you access" + " to services."; HashSet<Role> uniqueRoles = new HashSet<> (roles); for (Role role : uniqueRoles) { str += role.toString () + ", "; } str = str.substring (0, str.length () - 2); return str; } private String getRestrictionsAsString (Set<AccessRestriction> ars) { String restrictions = ""; if (ars != null && !ars.isEmpty ()) { restrictions = "The user has following restriction(s):\n"; for (AccessRestriction ar : ars) { restrictions += " - \"" + ar.getBlockingReason () + "\"\n"; } } return restrictions; } private String checkedStr (String prefix, String str, String postfix) { if (str != null && !str.trim ().isEmpty ()) return prefix + str + postfix; else return ""; } public void generatePassword () { generatePassword (8); } private void generatePassword (int length) { if (length < 1) throw new IllegalArgumentException ("Password length < 1 (" + length + ")."); final char[] buf; buf = new char[length]; for (int idx = 0; idx < buf.length; ++idx) buf[idx] = SYMBOLS[RANDOM.nextInt (SYMBOLS.length)]; tmpPassword = new String (buf); setPassword (new String (buf)); } public String hash () { String source = getUUID() + "-" + getUsername () + "@" + getEmail () + " - " + getPassword (); MessageDigest md; byte[] digest; try { md = MessageDigest.getInstance ("MD5"); digest = md.digest (source.getBytes ()); } catch (NoSuchAlgorithmException e) { throw new UnsupportedOperationException ( "Cannot compute MD5 digest for user " + getUsername (), e); } StringBuffer sb = new StringBuffer (); for (int i = 0; i < digest.length; ++i) { sb.append (Integer.toHexString ( (digest[i] & 0xFF) | 0x100) .substring (1, 3)); } return sb.toString (); } public String getCountry () { return country; } public void setCountry (String country) { this.country = country; } public String getDomain () { return domain; } public void setDomain (String domain) { this.domain = domain; } public String getSubDomain () { return subDomain; } public void setSubDomain (String sub_domain) { this.subDomain = sub_domain; } public String getUsage () { return usage; } public void setUsage (String usage) { this.usage = usage; } public String getSubUsage () { return subUsage; } public void setSubUsage (String sub_usage) { this.subUsage = sub_usage; } public String getDisplayableUsage () { if (usage == null) { return "unknown"; } if ("other".equals (usage.toLowerCase ())) { return subUsage; } return usage; } public String getDisplayableDomain () { if (domain == null) { return "unknown"; } if ("other".equals (domain.toLowerCase ())) { return subDomain; } return domain; } @Override public int hashCode () { final int prime = 31; int result = 1; result = prime * result + ( (uuid == null) ? 0 : uuid.hashCode ()); result = prime * result + ( (username == null) ? 0 : username.hashCode ()); return result; } @Override public boolean equals (Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass () != obj.getClass ()) return false; User other = (User) obj; if (uuid == null) { if (other.uuid != null) return false; } else if ( !uuid.equals (other.uuid)) return false; if (username == null) { if (other.username != null) return false; } else if ( !username.equals (other.username)) return false; return true; } }