Yay! Yet another change to the password system. This time we're switching

entirely from old-and-busted crypt() to the new hotness MD5. This allows
us to interoperate cleanly with our soon-to-be new forum system which MD5
encodes a user's password before it ever leaves the web browser. Those
security conscious little devils.


git-svn-id: https://samskivert.googlecode.com/svn/trunk@1551 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
mdb
2004-12-18 02:33:10 +00:00
parent 37858624b8
commit a495745c67
6 changed files with 96 additions and 76 deletions
@@ -44,6 +44,6 @@ public interface Authenticator
* the authentication check. * the authentication check.
*/ */
public void authenticateUser ( public void authenticateUser (
User user, String username, String password, boolean persist) User user, String username, Password password, boolean persist)
throws AuthenticationFailedException; throws AuthenticationFailedException;
} }
@@ -10,22 +10,12 @@ package com.samskivert.servlet.user;
public class Password public class Password
{ {
/** /**
* Creates a password from the supplied username and (unencrypted) * Returns the clear password text. This method may return null if
* password. * none was provided when creating the password.
*/ */
public Password (String username, String password) public String getCleartext ()
{ {
_encrypted = UserUtil.encryptPassword(username, password); return _cleartext;
}
/**
* Creates a password directly from the encrypted text. This text
* <em>must</em> have been created using {@link
* UserUtil#encryptPassword} or via the same algorithm.
*/
public Password (String encrypted)
{
_encrypted = encrypted;
} }
/** /**
@@ -36,5 +26,31 @@ public class Password
return _encrypted; return _encrypted;
} }
/**
* Creates a password instance from the supplied plaintext.
*/
public static Password makeFromClear (String password)
{
return new Password(password, UserUtil.encryptPassword(password));
}
/**
* Creates a password instance from the supplied already encrypted
* text. <em>Note:</em> the encrypted text must be obtained from
* {@link UserUtil#encryptPassword}.
*/
public static Password makeFromCrypto (String encrypted)
{
return new Password(null, encrypted);
}
/** Creates a password instance. */
protected Password (String cleartext, String encrypted)
{
_cleartext = cleartext;
_encrypted = encrypted;
}
protected String _cleartext;
protected String _encrypted; protected String _encrypted;
} }
@@ -76,7 +76,17 @@ public class User
*/ */
public void setPassword (String password) public void setPassword (String password)
{ {
this.password = UserUtil.encryptPassword(username, password); setPassword(Password.makeFromClear(password));
}
/**
* Updates the user's password.
*
* @param password The user's new password.
*/
public void setPassword (Password password)
{
this.password = password.getEncrypted();
_dirty.setModified("password"); _dirty.setModified("password");
} }
@@ -99,20 +109,38 @@ public class User
} }
/** /**
* Compares the supplied (unencrypted) password with the password * Converts a legacy password.
* associated with this user record. *
* @return true if a conversion took place, false if not.
*/
public boolean updateLegacyPassword (String password)
{
// we may have an old crypt() encrypted password
if (this.password.length() == 13) {
// check both the case sensitive and insensitive versions for
// further legacy reasons
if (this.password.equals(
UserUtil.legacyEncrypt(username, password, false)) ||
this.password.equals(
UserUtil.legacyEncrypt(username, password, true))) {
// update our password with the new encryption method
setPassword(password);
return true;
}
}
return false;
}
/**
* Compares the supplied password with the password associated with
* this user record.
* *
* @return true if the passwords match, false if they do not. * @return true if the passwords match, false if they do not.
*/ */
public boolean passwordsMatch (String password) public boolean passwordsMatch (Password password)
{ {
String epasswd = UserUtil.encryptPassword(username, password); return this.password.equals(password.getEncrypted());
String oldpasswd = UserUtil.encryptPassword(username, password, false);
// because we used to be case sensitive, we have to check both
// versions of the encrypted password here for backward
// compatibility
return (this.password.equals(epasswd) ||
this.password.equals(oldpasswd));
} }
/** /**
@@ -60,7 +60,7 @@ public class UserManager
{ {
// documentation inherited // documentation inherited
public void authenticateUser ( public void authenticateUser (
User user, String username, String password, boolean persist) User user, String username, Password password, boolean persist)
throws InvalidPasswordException throws InvalidPasswordException
{ {
// don't care // don't care
@@ -75,7 +75,7 @@ public class UserManager
{ {
// documentation inherited // documentation inherited
public void authenticateUser ( public void authenticateUser (
User user, String username, String password, boolean persist) User user, String username, Password password, boolean persist)
throws AuthenticationFailedException throws AuthenticationFailedException
{ {
if (!user.passwordsMatch(password)) { if (!user.passwordsMatch(password)) {
@@ -224,7 +224,7 @@ public class UserManager
* or some other error occurs, an exception will be thrown. * or some other error occurs, an exception will be thrown.
* *
* @param username The username supplied by the user. * @param username The username supplied by the user.
* @param password The plaintext password supplied by the user. * @param password The password supplied by the user.
* @param persist If true, the cookie will expire in one month, if * @param persist If true, the cookie will expire in one month, if
* false, the cookie will expire at the end of the user's browser * false, the cookie will expire at the end of the user's browser
* session. * session.
@@ -235,7 +235,7 @@ public class UserManager
* *
* @return the user object of the authenticated user. * @return the user object of the authenticated user.
*/ */
public User login (String username, String password, boolean persist, public User login (String username, Password password, boolean persist,
HttpServletRequest req, HttpServletResponse rsp, HttpServletRequest req, HttpServletResponse rsp,
Authenticator auth) Authenticator auth)
throws PersistenceException, AuthenticationFailedException throws PersistenceException, AuthenticationFailedException
@@ -246,6 +246,13 @@ public class UserManager
throw new NoSuchUserException("error.no_such_user"); throw new NoSuchUserException("error.no_such_user");
} }
// potentially convert the user's legacy password
if (password.getCleartext() != null &&
user.updateLegacyPassword(password.getCleartext())) {
Log.info("Updated legacy password " + user.username + ".");
_repository.updateUser(user);
}
// run the user through the authentication gamut // run the user through the authentication gamut
auth.authenticateUser(user, username, password, persist); auth.authenticateUser(user, username, password, persist);
@@ -91,29 +91,6 @@ public class UserRepository extends JORARepository
getUserClass().getName(), "users", session, "userId"); getUserClass().getName(), "users", session, "userId");
} }
/**
* Requests that a new user be created in the repository.
*
* @param uname the username of the new user to create.
* @param pass the (unencrypted) password for the new user.
* @param realname the user's real name.
* @param email the user's email address.
* @param siteId the unique identifier of the site through which this
* account is being created. The resulting user will be tracked as
* originating from this site for accounting purposes ({@link
* SiteIdentifier#DEFAULT_SITE_ID} can be used by systems that don't
* desire to perform site tracking.
*
* @return The userid of the newly created user.
*/
public int createUser (Username uname, String pass, String realname,
String email, int siteId)
throws UserExistsException, PersistenceException
{
return createUser(uname, new Password(uname.getUsername(), pass),
realname, email, siteId);
}
/** /**
* Like {@link #createUser(String,String,String,String,int)} except * Like {@link #createUser(String,String,String,String,int)} except
* that the supplied password has already been encrypted. * that the supplied password has already been encrypted.
@@ -139,11 +116,11 @@ public class UserRepository extends JORARepository
String realname, String email, int siteId) String realname, String email, int siteId)
{ {
user.username = name.getUsername(); user.username = name.getUsername();
user.password = pass.getEncrypted(); user.setPassword(pass);
user.realname = realname; user.setRealName(realname);
user.email = email; user.setEmail(email);
user.created = new Date(System.currentTimeMillis()); user.created = new Date(System.currentTimeMillis());
user.siteId = siteId; user.setSiteId(siteId);
} }
/** /**
@@ -606,10 +583,12 @@ public class UserRepository extends JORARepository
System.out.println(rep.loadUser("mdb")); System.out.println(rep.loadUser("mdb"));
System.out.println(rep.loadUserBySession("auth")); System.out.println(rep.loadUserBySession("auth"));
rep.createUser(new Username("samskivert"), "foobar", rep.createUser(new Username("samskivert"),
Password.makeFromClear("foobar"),
"Michael Bayne", "mdb@samskivert.com", "Michael Bayne", "mdb@samskivert.com",
SiteIdentifier.DEFAULT_SITE_ID); SiteIdentifier.DEFAULT_SITE_ID);
rep.createUser(new Username("mdb"), "foobar", "Michael Bayne", rep.createUser(new Username("mdb"),
Password.makeFromClear("foobar"), "Michael Bayne",
"mdb@samskivert.com", "mdb@samskivert.com",
SiteIdentifier.DEFAULT_SITE_ID); SiteIdentifier.DEFAULT_SITE_ID);
@@ -48,26 +48,17 @@ public class UserUtil
* that would be stored in the user record were the password to be * that would be stored in the user record were the password to be
* updated via {@link User#setPassword}. * updated via {@link User#setPassword}.
*/ */
public static String encryptPassword (String username, String password) public static String encryptPassword (String password)
{ {
return encryptPassword(username, password, true); return StringUtil.md5hex(password);
} }
/** /**
* Encrypts the user's password according to our preferred scheme. * Encrypts passwords the way we used to.
*
* @param username the username of the user whose password is to be
* encrypted.
* @param password the plaintext of the password to be encrypted.
* @param ignoreUserCase true if the username should be uncapitalized
* prior to performing the encryption so that future password checks
* will work if the user registers as "Bluebeard" but logs in as
* "bluebeard".
*/ */
public static String encryptPassword ( public static String legacyEncrypt (String username, String password,
String username, String password, boolean ignoreUserCase) boolean ignoreUserCase)
{ {
// perhaps we'll not be too (case) sensitive
if (ignoreUserCase) { if (ignoreUserCase) {
username = username.toLowerCase(); username = username.toLowerCase();
} }
@@ -77,10 +68,9 @@ public class UserUtil
public static void main (String[] args) public static void main (String[] args)
{ {
if (args.length < 2) { if (args.length < 2) {
System.err.println("Usage: UserUtil username password"); System.err.println("Usage: UserUtil password");
System.exit(-1); System.exit(-1);
} }
System.out.println("Encrypted password: " + System.out.println("Encrypted password: " + encryptPassword(args[0]));
encryptPassword(args[0], args[1], true));
} }
} }