diff --git a/projects/samskivert/src/java/com/samskivert/servlet/user/InvalidPasswordException.java b/projects/samskivert/src/java/com/samskivert/servlet/user/InvalidPasswordException.java new file mode 100644 index 00000000..37730693 --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/servlet/user/InvalidPasswordException.java @@ -0,0 +1,12 @@ +// +// $Id: InvalidPasswordException.java,v 1.1 2001/03/02 02:08:50 mdb Exp $ + +package com.samskivert.servlet.user; + +public class InvalidPasswordException extends Exception +{ + public InvalidPasswordException (String message) + { + super(message); + } +} diff --git a/projects/samskivert/src/java/com/samskivert/servlet/user/Makefile b/projects/samskivert/src/java/com/samskivert/servlet/user/Makefile index 362aa764..1d1c2f32 100644 --- a/projects/samskivert/src/java/com/samskivert/servlet/user/Makefile +++ b/projects/samskivert/src/java/com/samskivert/servlet/user/Makefile @@ -1,13 +1,16 @@ # -# $Id: Makefile,v 1.1 2001/03/02 01:21:06 mdb Exp $ +# $Id: Makefile,v 1.2 2001/03/02 02:08:50 mdb Exp $ ROOT = ../../../.. SRCS = \ + InvalidPasswordException.java \ InvalidUsernameException.java \ + NoSuchUserException.java \ User.java \ UserExistsException.java \ UserManager.java \ UserRepository.java \ + UserUtil.java \ include $(ROOT)/build/Makefile.java diff --git a/projects/samskivert/src/java/com/samskivert/servlet/user/NoSuchUserException.java b/projects/samskivert/src/java/com/samskivert/servlet/user/NoSuchUserException.java new file mode 100644 index 00000000..20df12bf --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/servlet/user/NoSuchUserException.java @@ -0,0 +1,12 @@ +// +// $Id: NoSuchUserException.java,v 1.1 2001/03/02 02:08:50 mdb Exp $ + +package com.samskivert.servlet.user; + +public class NoSuchUserException extends Exception +{ + public NoSuchUserException (String message) + { + super(message); + } +} diff --git a/projects/samskivert/src/java/com/samskivert/servlet/user/User.java b/projects/samskivert/src/java/com/samskivert/servlet/user/User.java index ce3687a5..be2973ca 100644 --- a/projects/samskivert/src/java/com/samskivert/servlet/user/User.java +++ b/projects/samskivert/src/java/com/samskivert/servlet/user/User.java @@ -1,5 +1,5 @@ // -// $Id: User.java,v 1.1 2001/03/02 01:21:06 mdb Exp $ +// $Id: User.java,v 1.2 2001/03/02 02:08:50 mdb Exp $ package com.samskivert.servlet.user; @@ -53,7 +53,7 @@ public class User */ public void setPassword (String password) { - this.password = UserRepository.encryptPassword(username, password); + this.password = UserUtil.encryptPassword(username, password); } /** @@ -63,4 +63,16 @@ public class User { this.email = email; } + + /** + * Compares the supplied password with the password associated with + * this user record. + * + * @return true if the passwords match, false if they do not. + */ + public boolean passwordsMatch (String password) + { + String epasswd = UserUtil.encryptPassword(username, password); + return this.password.equals(epasswd); + } } diff --git a/projects/samskivert/src/java/com/samskivert/servlet/user/UserManager.java b/projects/samskivert/src/java/com/samskivert/servlet/user/UserManager.java index e84d4243..eb41cea1 100644 --- a/projects/samskivert/src/java/com/samskivert/servlet/user/UserManager.java +++ b/projects/samskivert/src/java/com/samskivert/servlet/user/UserManager.java @@ -1,20 +1,16 @@ // -// $Id: UserManager.java,v 1.1 2001/03/02 01:21:06 mdb Exp $ +// $Id: UserManager.java,v 1.2 2001/03/02 02:08:50 mdb Exp $ package com.samskivert.servlet.user; import java.net.URLEncoder; import java.sql.SQLException; import java.util.Properties; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpUtils; +import javax.servlet.http.*; import com.samskivert.Log; import com.samskivert.servlet.RedirectException; -import com.samskivert.util.Interval; -import com.samskivert.util.IntervalManager; -import com.samskivert.util.StringUtil; +import com.samskivert.util.*; /** * The user manager provides easy access to user objects for servlets. It @@ -99,9 +95,9 @@ public class UserManager public User loadUser (HttpServletRequest req) throws SQLException { - String authcode = getUserAuthCookie(req); + String authcode = getAuthCode(req); if (authcode != null) { - return _repository.getUserBySession(authcode); + return _repository.loadUserBySession(authcode); } else { return null; } @@ -136,7 +132,55 @@ public class UserManager return user; } - protected static String getUserAuthCookie (HttpServletRequest req) + /** + * Attempts to authenticate the requester and initiate an + * authenticated session for them. An authenticated session involves + * their receiving a cookie that provides them to be authenticated and + * an entry in the session database being created that maps their + * information to their userid. If this call completes, the session + * was established and the proper cookies were set in the supplied + * response object. If invalid authentication information is provided + * or some other error occurs, an exception will be thrown. + */ + public void login (String username, String password, + HttpServletResponse rsp) + throws SQLException, NoSuchUserException, InvalidPasswordException + { + // load up the requested user + User user = _repository.loadUser(username); + if (user == null) { + throw new NoSuchUserException("error.no_such_user"); + } + if (!user.passwordsMatch(password)) { + throw new InvalidPasswordException("error.invalid_password"); + } + + // generate a new session for this user + String authcode = _repository.createNewSession(user); + // stick it into a cookie for their browsing convenience + Cookie acookie = new Cookie(USERAUTH_COOKIE, authcode); + acookie.setPath("/"); + acookie.setMaxAge(24*60*60); // expire in two days + rsp.addCookie(acookie); + } + + public void logout (HttpServletRequest req, HttpServletResponse rsp) + { + String authcode = getAuthCode(req); + + // nothing to do if they don't already have an auth cookie + if (authcode == null) { + return; + } + + // set them up the bomb + Cookie rmcookie = new Cookie(USERAUTH_COOKIE, authcode); + rmcookie.setPath("/"); + rmcookie.setMaxAge(0); + rsp.addCookie(rmcookie); + } + + protected static String getAuthCode (HttpServletRequest req) { Cookie[] cookies = req.getCookies(); if (cookies == null) { diff --git a/projects/samskivert/src/java/com/samskivert/servlet/user/UserRepository.java b/projects/samskivert/src/java/com/samskivert/servlet/user/UserRepository.java index 2a7f5c9a..7cf4a4f0 100644 --- a/projects/samskivert/src/java/com/samskivert/servlet/user/UserRepository.java +++ b/projects/samskivert/src/java/com/samskivert/servlet/user/UserRepository.java @@ -1,9 +1,10 @@ // -// $Id: UserRepository.java,v 1.1 2001/03/02 01:21:06 mdb Exp $ +// $Id: UserRepository.java,v 1.2 2001/03/02 02:08:50 mdb Exp $ package com.samskivert.servlet.user; import java.sql.*; +import java.util.Calendar; import java.util.Properties; import org.apache.regexp.*; @@ -11,7 +12,6 @@ import org.apache.regexp.*; import com.samskivert.Log; import com.samskivert.jdbc.MySQLRepository; import com.samskivert.jdbc.jora.*; -import com.samskivert.util.Crypt; public class UserRepository extends MySQLRepository { @@ -73,7 +73,7 @@ public class UserRepository extends MySQLRepository // create a new user object and stick it into the database final User user = new User(); user.username = username; - user.password = encryptPassword(username, password); + user.password = UserUtil.encryptPassword(username, password); user.realname = realname; user.email = email; user.created = new Date(System.currentTimeMillis()); @@ -99,21 +99,13 @@ public class UserRepository extends MySQLRepository return user.userid; } - /** - * Encrypts the user's password according to our preferred scheme. - */ - public static String encryptPassword (String username, String password) - { - return Crypt.crypt(username.substring(0, 2), password); - } - /** * Looks up a user by username. * * @return the user with the specified user id or null if no user with * that id exists. */ - public User getUser (String username) + public User loadUser (String username) throws SQLException { // look up the user @@ -135,7 +127,7 @@ public class UserRepository extends MySQLRepository * @return the user with the specified user id or null if no user with * that id exists. */ - public User getUser (int userid) + public User loadUser (int userid) throws SQLException { // look up the user @@ -157,7 +149,7 @@ public class UserRepository extends MySQLRepository * @return the user associated with the specified session or null of * no session exists with the supplied identifier. */ - public User getUserBySession (String sessionKey) + public User loadUserBySession (String sessionKey) throws SQLException { // look up the user @@ -203,6 +195,39 @@ public class UserRepository extends MySQLRepository }); } + /** + * Creates a new session for the specified user and returns the + * randomly generated session identifier for that session. Sessions + * are set to expire in two days which prevents someone from being + * screwed if they log in at 11:59pm, but also prevents them from + * leaving their browser authenticated for too long. + */ + public String createNewSession (final User user) + throws SQLException + { + // generate a random session identifier + final String authcode = UserUtil.genAuthCode(user); + + // figure out what 48 hours from now is + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.DATE, 2); + final Date expires = new Date(cal.getTime().getTime()); + + // insert the session into the database + execute(new Operation () { + public void invoke () throws SQLException + { + _session.execute("insert into sessions " + + "(authcode, userid, expires) values('" + + authcode + "', " + user.userid + ", " + + expires + ")"); + } + }); + + // and let the user know what the session identifier is + return authcode; + } + /** * Prunes any expired sessions from the sessions table. */ @@ -229,8 +254,8 @@ public class UserRepository extends MySQLRepository try { UserRepository rep = new UserRepository(props); - System.out.println(rep.getUser("mdb")); - System.out.println(rep.getUserBySession("auth")); + System.out.println(rep.loadUser("mdb")); + System.out.println(rep.loadUserBySession("auth")); rep.createUser("samskivert", "foobar", "Michael Bayne", "mdb@samskivert.com"); @@ -246,8 +271,6 @@ public class UserRepository extends MySQLRepository protected Table _utable; - protected static final int MINIMUM_USERNAME_LENGTH = 2; - /** Used to check usernames for invalid characteres. */ protected static RE _userre; static { @@ -257,4 +280,7 @@ public class UserRepository extends MySQLRepository Log.warning("Unable to initialize user regexp?! " + rese); } } + + /** The minimum allowable length of a username. */ + protected static final int MINIMUM_USERNAME_LENGTH = 3; } diff --git a/projects/samskivert/src/java/com/samskivert/servlet/user/UserUtil.java b/projects/samskivert/src/java/com/samskivert/servlet/user/UserUtil.java new file mode 100644 index 00000000..fa4cb28a --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/servlet/user/UserUtil.java @@ -0,0 +1,47 @@ +// +// $Id: UserUtil.java,v 1.1 2001/03/02 02:08:50 mdb Exp $ + +package com.samskivert.servlet.user; + +import java.security.*; + +import com.samskivert.util.Crypt; +import com.samskivert.util.StringUtil; + +/** + * User related utility functions. + */ +public class UserUtil +{ + /** + * Generates a new random session identifier for the supplied user. + */ + public static String genAuthCode (User user) + { + // concatenate a bunch of secret stuff together + StringBuffer buf = new StringBuffer(); + buf.append(user.password); + buf.append(System.currentTimeMillis()); + buf.append(Math.random()); + // and MD5 hash it + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + byte[] enc = digest.digest(buf.toString().getBytes()); + return StringUtil.hexlate(enc); + + } catch (NoSuchAlgorithmException nsae) { + throw new RuntimeException("JVM missing MD5 message digest " + + "algorithm implementation. User " + + "management facilities require MD5 " + + "encoding capabilities."); + } + } + + /** + * Encrypts the user's password according to our preferred scheme. + */ + public static String encryptPassword (String username, String password) + { + return Crypt.crypt(username.substring(0, 2), password); + } +}