/******************************************************************************* * Copyright 2013 Open mHealth * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package org.openmhealth.reference.domain; import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; import jbcrypt.BCrypt; import org.openmhealth.reference.exception.OmhException; import org.openmhealth.reference.util.OmhObjectMapper; import org.openmhealth.reference.util.OmhObjectMapper.JacksonFieldFilter; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonFilter; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; /** * <p> * A user in the system. * </p> * * @author John Jenkins */ @JsonFilter(User.JACKSON_FILTER_GROUP_ID) public class User implements OmhObject { /** * The version of this class for serialization purposes. */ private static final long serialVersionUID = 1L; /** * The number of rounds for BCrypt to use when generating a salt. */ private static final int BCRYPT_SALT_ROUNDS = 12; /** * The group ID for the Jackson filter. This must be unique to our class, * whatever the value is. */ protected static final String JACKSON_FILTER_GROUP_ID = "org.openmhealth.reference.domain.User"; // Register this class with the Open mHealth object mapper. static { OmhObjectMapper.register(User.class); } /** * The JSON key for the user's user-name. */ public static final String JSON_KEY_USERNAME = "username"; /** * The JSON key for the user's password. */ public static final String JSON_KEY_PASSWORD = "password"; /** * The JSON key for the user's email address. */ public static final String JSON_KEY_EMAIL = "email"; /** * The JSON key for the registration key for this user. */ public static final String JSON_KEY_REGISTRATION_KEY = "registration_key"; /** * The JSON key for the date the user's account was registered. */ public static final String JSON_KEY_DATE_REGISTERED = "date_registered"; /** * The JSON key for the date the user's account was activated. */ public static final String JSON_KEY_DATE_ACTIVATED = "date_activated"; /** * The user's user-name. */ @JsonProperty(JSON_KEY_USERNAME) private final String username; /** * The user's password. */ @JsonProperty(JSON_KEY_PASSWORD) @JacksonFieldFilter(JACKSON_FILTER_GROUP_ID) private final String password; /** * The user's email address. */ @JsonProperty(JSON_KEY_EMAIL) @JsonSerialize(using = ToStringSerializer.class) private final InternetAddress emailAddress; /** * The registration key for this account. This may be null if the account * was created in some way that did not require an activation link. */ @JsonProperty(JSON_KEY_REGISTRATION_KEY) private final String registrationKey; /** * The date this account was registered. This may be null if the account * was never required to be activated. */ @JsonProperty(JSON_KEY_DATE_REGISTERED) private final Long dateRegistered; /** * The date this account was activated. This may be null if the account was * never activated either because the account did not require activation or * because the user has not yet activated it. */ @JsonProperty(JSON_KEY_DATE_ACTIVATED) private Long dateActivated; /** * Creates a new user. * * @param username * This user's user-name. * * @param password * This user's password. * * @param email * The user's email address. * * @param registrationKey * The unique key generated when this account was registered. This * may be null if this account was never required to be activated. * * @param dateRegistered * The date and time that this account was registered. This may be * null if this account was never required to be activated. * * @param dateActivated * The date and time that this account was activated. This may be * null if this account was never required to be activated or if the * owner has not yet registered it. * * @throws OmhException * The user-name was invalid. */ @JsonCreator public User( @JsonProperty(JSON_KEY_USERNAME) final String username, @JsonProperty(JSON_KEY_PASSWORD) final String password, @JsonProperty(JSON_KEY_EMAIL) final String email, @JsonProperty(JSON_KEY_REGISTRATION_KEY) final String registrationKey, @JsonProperty(JSON_KEY_DATE_REGISTERED) final Long dateRegistered, @JsonProperty(JSON_KEY_DATE_ACTIVATED) final Long dateActivated) throws OmhException { // Validate and store the user-name. this.username = validateUsername(username); // Validate and store the password. this.password = validatePassword(password); // Validate and store the email address. this.emailAddress = validateEmail(email); // Verify that the registration key and date align. if(registrationKey == null) { if(dateRegistered != null) { throw new OmhException( "The account does not have a registration key but " + "has a registration date."); } } else { if(dateRegistered == null) { throw new OmhException( "The account has a registration key but does not " + "have a registration date."); } // Verify that if a registration key was given that it is not empty. if(registrationKey.length() == 0) { throw new OmhException("The registration key is empty."); } } // Verify that the account was not registered after it was activated. if( (dateRegistered != null) && (dateActivated != null) && dateRegistered > dateActivated) { throw new OmhException( "The account was activated before it was registered."); } // Store the registration and activation information. this.registrationKey = registrationKey; this.dateRegistered = dateRegistered; this.dateActivated = dateActivated; } /** * Returns the user's user-name. * * @return The user's user-name. */ public String getUsername() { return username; } /** * Returns the user's hashed password. * * @return The user's hashed password. */ public String getPassword() { return password; } /** * Returns the user's email address. * * @return The user's email address. */ public InternetAddress getEmail() { return emailAddress; } /** * Returns the generated key when the user was registered. * * @return The generated key when the user was registered. */ public String getRegistratioKey() { return registrationKey; } /** * Returns whether or not this account has been activated. * * @return Whether or not this account has been activated. */ public boolean isActivated() { return ((registrationKey == null) || (dateActivated != null)); } /** * Returns the date and time that the account was registered or null if it * was never required to be activated. If {@link #isActivated()} returns * true, then this account was never required to be activated. * * @return The date and time that this account was registered or null. */ public Long getDateRegistered() { return dateRegistered; } /** * Returns the date and time that the account was activated or null if it * was never activated. If {@link #isActivated()} returns true, then this * account was never required to activate; otherwise, the user has not yet * activated. * * @return The date and time that this account was required to be * registered or null. */ public Long getDateActivated() { return dateActivated; } public void activate() throws OmhException { if(isActivated()) { throw new OmhException("The account has already been activated."); } if(registrationKey == null) { throw new OmhException( "This account is not elegible for activation."); } dateActivated = System.currentTimeMillis(); } /** * Checks if a given password matches this user's password. * * @param password * The plain-text password to check. * * @return True if the passwords match; false, otherwise. * * @throws OmhException * The password is null. */ public boolean checkPassword(final String password) throws OmhException { // Validate the parameter. String validatedPassword = validatePassword(password); // Use BCrypt to check the password. return BCrypt.checkpw(validatedPassword, this.password); } /** * Verifies that a user-name is valid. * * @param username * The user-name to validate. * * @return The trimmed user-name. * * @throws OmhException * The user-name was invalid. */ public static String validateUsername( final String username) throws OmhException { if(username == null) { throw new OmhException("The username is null."); } String trimmedUsername = username.trim(); if(trimmedUsername.length() == 0) { throw new OmhException("The username is empty."); } return trimmedUsername; } /** * Verifies that a password is not null or empty. This can be used for both * un-hashed and hashed passwords. This is not designed to hash passwords; * for that, use {@link #hashPassword(String)}. This is not designed to * verify that a plain-text password is the same as this user's password; * for that, use {@link #checkPassword(String)}. * * @param password The password to verify. * * @return The password exactly as it was given. * * @throws OmhException The password is null or empty. */ public static String validatePassword( final String password) throws OmhException { if(password == null) { throw new OmhException("The password is null."); } if(password.length() == 0) { throw new OmhException("The password is empty."); } return password; } /** * Hashes a plain-text password. * * @param plaintextPassword * The plain-text password to hash. * * @return The hashed password. * * @throws OmhException * The given password was invalid. */ public static String hashPassword( final String plaintextPassword) throws OmhException { String validatedPassword = validatePassword(plaintextPassword); return BCrypt .hashpw(validatedPassword, BCrypt.gensalt(BCRYPT_SALT_ROUNDS)); } /** * Verifies that an email address is a valid email address, but it does not * guarantee that the location that it references exists. * * @param email * The email address to be validated. * * @return The email address as an InternetAddress object. * * @throws OmhException * The email address is invalid. */ public static InternetAddress validateEmail( final String email) throws OmhException { if(email == null) { throw new OmhException("The email address is null"); } String trimmedEmail = email.trim(); if(trimmedEmail.length() == 0) { throw new OmhException("The email address is empty."); } // Create the InternetAddress object. InternetAddress result; try { result = new InternetAddress(email); } catch(AddressException e) { throw new OmhException( "The email address is not a valid email address.", e); } // Validate the address. try { result.validate(); } catch(AddressException e) { throw new OmhException( "The email address is not a valid email address.", e); } return result; } // public static class UserJacksonFieldFilter implements JacksonFieldFilter { // public static final String FILTER_ID = // "org.openmhealth.reference.domain.User"; // // /** // * A private constructor to ensure that only the User class ever // * instantiates this. // */ // private UserJacksonFieldFilter() {} // // /* // * (non-Javadoc) // * @see org.openmhealth.reference.util.OmhObjectMapper.JacksonFieldFilter#getFilterId() // */ // @Override // public String getFilterId() { // return FILTER_ID; // } // // /* // * (non-Javadoc) // * @see org.openmhealth.reference.util.OmhObjectMapper.JacksonFieldFilter#getFieldNames() // */ // @Override // public Set<String> getFieldNames() { // Set<String> fieldNames = new HashSet<String>(); // fieldNames.add(JSON_KEY_PASSWORD); // return fieldNames; // } // } }