package hirondelle.fish.access.user;
import java.util.regex.*;
import java.util.logging.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import hirondelle.web4j.model.ModelCtorException;
import hirondelle.web4j.model.ModelUtil;
import hirondelle.web4j.util.Util;
import hirondelle.web4j.model.Check;
import hirondelle.web4j.model.Validator;
import hirondelle.web4j.security.SafeText;
import static hirondelle.web4j.util.Consts.FAILS;
public final class User {
static {
MessageDigest sha = null;
try {
sha = MessageDigest.getInstance("SHA-1");
}
catch (NoSuchAlgorithmException ex){
throw new RuntimeException("Unable to hash passwords. MessageDigest class cannot find the SHA-1 hash function.");
}
}
public User(SafeText aName, SafeText aHashedPassword) throws ModelCtorException {
fName = aName;
fHashedPassword = aHashedPassword;
validateState();
}
public static User forNewUserOrPasswordReset(SafeText aName) throws ModelCtorException {
return new User(aName, SafeText.from(hash(MAGIC_INITIAL_PASSWORD)));
}
public static User forPasswordChange(SafeText aName, SafeText aClearTextPassword) throws ModelCtorException {
User temp = new User(aName, aClearTextPassword); return new User(aName, SafeText.from(hash(aClearTextPassword.getRawString())));
}
public SafeText getName() {
return fName;
}
public SafeText getPassword() {
return fHashedPassword;
}
public boolean isResetValue(){
return hash(MAGIC_INITIAL_PASSWORD).equalsIgnoreCase(fHashedPassword.getRawString());
}
@Override public String toString() {
return hirondelle.fish.access.user.User.class + " User Name : " + fName + " Password : ****";
}
@Override public boolean equals( Object aThat ) {
Boolean result = ModelUtil.quickEquals(this, aThat);
if ( result == null ){
User that = (User) aThat;
result = ModelUtil.equalsFor(this.getSignificantFields(), that.getSignificantFields());
}
return result;
}
@Override public int hashCode() {
if ( fHashCode == 0 ) {
fHashCode = ModelUtil.hashCodeFor(getSignificantFields());
}
return fHashCode;
}
private final SafeText fName;
private final SafeText fHashedPassword;
private int fHashCode;
private static final String MAGIC_INITIAL_PASSWORD = "changemetosomethingalotmoreconvenienttotype";
private static final Pattern ACCEPTED_PATTERN = Pattern.compile("(?:\\S){6,50}");
private static final Logger fLogger = Util.getLogger(User.class);
private void validateState() throws ModelCtorException {
ModelCtorException ex = new ModelCtorException();
Validator validPattern = Check.pattern(ACCEPTED_PATTERN);
if ( FAILS == Check.required(fName, validPattern) ) {
ex.add("Name is required, 6..50 chars, no spaces.");
}
if ( FAILS == Check.required(fHashedPassword, validPattern)) {
ex.add("Password is required, 6..50 chars, no spaces.");
}
if( fName != null && fHashedPassword != null ) {
if( fName.getRawString().equalsIgnoreCase(fHashedPassword.getRawString()) ){
ex.add("Password cannot be the same as the user name.");
}
}
if ( ! ex.isEmpty() ) throw ex;
}
private Object[] getSignificantFields(){
return new Object[] {fName, fHashedPassword};
}
private static String hash(String aCleartext) {
String result = null;
MessageDigest sha = null;
try {
sha = MessageDigest.getInstance("SHA-1");
}
catch (NoSuchAlgorithmException ex){
fLogger.severe("Cannot find SHA-1 hash function.");
}
if (sha != null){
byte[] digest = sha.digest( aCleartext.getBytes() );
result = hexEncode(digest);
}
else {
result = aCleartext;
}
return result;
}
private static String hexEncode( byte[] aInput){
StringBuffer result = new StringBuffer();
char[] digits = {'0', '1', '2', '3', '4','5','6','7','8','9','a','b','c','d','e','f'};
for ( int idx = 0; idx < aInput.length; ++idx) {
byte b = aInput[idx];
result.append( digits[ (b&0xf0) >> 4 ] );
result.append( digits[ b&0x0f] );
}
return result.toString();
}
private static void main(String[] args){
System.out.println("Hash: " + hash("testtest"));
}
}