Created initial revision of user management services.

git-svn-id: https://samskivert.googlecode.com/svn/trunk@67 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
mdb
2001-03-02 01:21:06 +00:00
parent de2f079b79
commit 878234e6f8
8 changed files with 559 additions and 0 deletions
@@ -0,0 +1,9 @@
#
# $Id: Makefile,v 1.1 2001/03/02 01:21:06 mdb Exp $
ROOT = ../../..
SRCS = \
RedirectException.java \
include $(ROOT)/build/Makefile.java
@@ -0,0 +1,25 @@
//
// $Id: RedirectException.java,v 1.1 2001/03/02 01:21:06 mdb Exp $
package com.samskivert.servlet;
/**
* A redirect exception is thrown by servlet services when they require
* that the user be redirected to a different URL rather than continue
* processing this request. It is expected that redirect handling can be
* implemented in a single place such that servlets can simply allow this
* exception to propagate up to the proper handler which will then issue
* the appropriate redirect header.
*/
public class RedirectException extends Exception
{
public RedirectException (String url)
{
super(url);
}
public String getRedirectURL ()
{
return getMessage();
}
}
@@ -0,0 +1,12 @@
//
// $Id: InvalidUsernameException.java,v 1.1 2001/03/02 01:21:06 mdb Exp $
package com.samskivert.servlet.user;
public class InvalidUsernameException extends Exception
{
public InvalidUsernameException (String message)
{
super(message);
}
}
@@ -0,0 +1,13 @@
#
# $Id: Makefile,v 1.1 2001/03/02 01:21:06 mdb Exp $
ROOT = ../../../..
SRCS = \
InvalidUsernameException.java \
User.java \
UserExistsException.java \
UserManager.java \
UserRepository.java \
include $(ROOT)/build/Makefile.java
@@ -0,0 +1,66 @@
//
// $Id: User.java,v 1.1 2001/03/02 01:21:06 mdb Exp $
package com.samskivert.servlet.user;
import java.sql.Date;
/**
* A user object contains information about a registered user in our web
* application environment. Users are stored in the user repository (a
* database table) and loaded during a request based on authentication
* information provided with the request headers.
*
* <p><b>Note:</b> Do not modify any of the fields of this object
* diretly. Use the <code>set</code> methods to make updates. If no set
* methods exist, you shouldn't be modifying that field.
*/
public class User
{
/** The user's assigned integer userid. */
public int userid;
/** The user's chosen username. */
public String username;
/** The date this record was created. */
public Date created;
/**
* The user's real name (first, last and whatever else they opt to
* provide).
*/
public String realname;
/** The user's chosen password (encrypted). */
public String password;
/** The user's email address. */
public String email;
/**
* Updates the user's real name.
*/
public void setRealName (String realname)
{
this.realname = realname;
}
/**
* Updates the user's password.
*
* @param password The user's new (unencrypted) password.
*/
public void setPassword (String password)
{
this.password = UserRepository.encryptPassword(username, password);
}
/**
* Updates the user's email address.
*/
public void setEmail (String email)
{
this.email = email;
}
}
@@ -0,0 +1,12 @@
//
// $Id: UserExistsException.java,v 1.1 2001/03/02 01:21:06 mdb Exp $
package com.samskivert.servlet.user;
public class UserExistsException extends Exception
{
public UserExistsException (String message)
{
super(message);
}
}
@@ -0,0 +1,162 @@
//
// $Id: UserManager.java,v 1.1 2001/03/02 01:21:06 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 com.samskivert.Log;
import com.samskivert.servlet.RedirectException;
import com.samskivert.util.Interval;
import com.samskivert.util.IntervalManager;
import com.samskivert.util.StringUtil;
/**
* The user manager provides easy access to user objects for servlets. It
* takes care of cookie management involved in login, logout and loading a
* user record during an authenticated session.
*/
public class UserManager
{
/**
* A user manager creates a user repository through which to load and
* save user records. The properties needed to configure the user
* repository must be provided to the user manager at construct time.
*
* <p> Presently the user manager requires the following configuration
* information:
* <ul>
* <li><code>login_url</code>: Should be set to the URL to which to
* redirect a requester if they are required to login before accessing
* the requested page. For example:
*
* <pre>
* login_url = /usermgmt/login.ajsp?return=%R
* </pre>
*
* The <code>%R</code> will be replaced with the URL encoded URL the
* user is currently requesting (complete with query parameters) so
* that the login code can redirect the user back to this request once
* they are authenticated.
* </ul>
*
* @see UserRepository#UserRepository
*/
public UserManager (Properties props)
throws SQLException
{
// open up the user repository
_repository = new UserRepository(props);
// fetch the login URL from the properties
_loginURL = props.getProperty("login_url");
if (_loginURL == null) {
Log.warning("No login_url supplied in user manager config. " +
"Authentication won't work.");
}
// register a cron job to prune the session table every hour
Interval pruner = new Interval() {
public void intervalExpired (int id, Object arg)
{
try {
_repository.pruneSessions();
} catch (SQLException sqe) {
Log.warning("Error pruning session table: " + sqe);
}
}
};
_prunerid = IntervalManager.register(pruner, SESSION_PRUNE_INTERVAL,
null, true);
}
public void shutdown ()
{
// shut down the user repository
try {
_repository.shutdown();
} catch (SQLException sqe) {
Log.warning("Error shutting down user repository: " + sqe);
}
// cancel our session table pruning thread
IntervalManager.remove(_prunerid);
}
/**
* Fetches the necessary authentication information from the http
* request and loads the user identified by that information.
*
* @return the user associated with the request or null if no user was
* associated with the request or if the authentication information is
* bogus.
*/
public User loadUser (HttpServletRequest req)
throws SQLException
{
String authcode = getUserAuthCookie(req);
if (authcode != null) {
return _repository.getUserBySession(authcode);
} else {
return null;
}
}
/**
* Fetches the necessary authentication information from the http
* request and loads the user identified by that information. If no
* user could be loaded (because the requester is not authenticated),
* a redirect exception will be thrown to redirect the user to the
* login page specified in the user manager configuration.
*
* @return the user associated with the request.
*/
public User requireUser (HttpServletRequest req)
throws SQLException, RedirectException
{
User user = loadUser(req);
// if no user was loaded, we need to redirect these fine people to
// the login page
if (user == null) {
// first construct the redirect URL
StringBuffer rurl = HttpUtils.getRequestURL(req);
String qs = req.getQueryString();
if (qs != null) {
rurl.append("?").append(qs);
}
String eurl = URLEncoder.encode(rurl.toString());
String target = StringUtil.replace(_loginURL, "%R", eurl);
throw new RedirectException(target);
}
return user;
}
protected static String getUserAuthCookie (HttpServletRequest req)
{
Cookie[] cookies = req.getCookies();
if (cookies == null) {
return null;
}
for (int i = 0; i < cookies.length; i++) {
if (cookies[i].getName().equals(USERAUTH_COOKIE)) {
return cookies[i].getValue();
}
}
return null;
}
protected UserRepository _repository;
protected int _prunerid = -1;
protected String _loginURL;
protected static final String USERAUTH_COOKIE = "id_";
/** Prune the session table every hour. */
protected static final long SESSION_PRUNE_INTERVAL = 60L * 60L * 1000L;
}
@@ -0,0 +1,260 @@
//
// $Id: UserRepository.java,v 1.1 2001/03/02 01:21:06 mdb Exp $
package com.samskivert.servlet.user;
import java.sql.*;
import java.util.Properties;
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
{
/**
* Creates the repository and opens the user database. A properties
* object should be supplied with the following fields:
*
* <pre>
* driver=[jdbc driver class]
* url=[jdbc driver url]
* username=[jdbc username]
* password=[jdbc password]
* </pre>
*
* @param props a properties object containing the configuration
* parameters for the repository.
*/
public UserRepository (Properties props)
throws SQLException
{
super(props);
}
protected void createTables ()
throws SQLException
{
// create our table object
_utable = new Table(User.class.getName(), "users", _session,
"userid");
}
/**
* Requests that a new user be created in the repository.
*
* @param username The username of the new user to create. Usernames
* must consist only of characters that match the following regular
* expression: <code>[_A-Za-z0-9]+</code> and be longer than two
* characters.
* @param password The (unencrypted) password for the new user.
* @param realname The user's real name.
* @param email The user's email address.
*
* @return The userid of the newly created user.
*/
public int createUser (String username, String password,
String realname, String email)
throws InvalidUsernameException, UserExistsException, SQLException
{
// check minimum length
if (username.length() < MINIMUM_USERNAME_LENGTH) {
throw new InvalidUsernameException("error.username_too_short");
}
// check that it's only valid characters
if (!_userre.match(username)) {
throw new InvalidUsernameException("error.invalid_username");
}
// 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.realname = realname;
user.email = email;
user.created = new Date(System.currentTimeMillis());
try {
execute(new Operation () {
public void invoke () throws SQLException
{
_utable.insert(user);
// update the userid now that it's known
user.userid = lastInsertedId();
}
});
} catch (SQLException sqe) {
if (isDuplicateRowException(sqe)) {
throw new UserExistsException("error.user_exists");
} else {
throw sqe;
}
}
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)
throws SQLException
{
// look up the user
Cursor ec = _utable.select("where username = '" + username + "'");
// fetch the user from the cursor
User user = (User)ec.next();
if (user != null) {
// call next() again to cause the cursor to close itself
ec.next();
}
return user;
}
/**
* Looks up a user by userid.
*
* @return the user with the specified user id or null if no user with
* that id exists.
*/
public User getUser (int userid)
throws SQLException
{
// look up the user
Cursor ec = _utable.select("where userid = " + userid);
// fetch the user from the cursor
User user = (User)ec.next();
if (user != null) {
// call next() again to cause the cursor to close itself
ec.next();
}
return user;
}
/**
* Looks up a user by a session identifier.
*
* @return the user associated with the specified session or null of
* no session exists with the supplied identifier.
*/
public User getUserBySession (String sessionKey)
throws SQLException
{
// look up the user
Cursor ec = _utable.select("sessions",
"where authcode = '" + sessionKey +
"' AND sessions.userid = users.userid");
// fetch the user from the cursor
User user = (User)ec.next();
if (user != null) {
// call next() again to cause the cursor to close itself
ec.next();
}
return user;
}
/**
* Updates a user that was previously fetched from the repository.
*/
public void updateUser (final User user)
throws SQLException
{
execute(new Operation () {
public void invoke () throws SQLException
{
_utable.update(user);
}
});
}
/**
* Removes the user from the repository.
*/
public void deleteUser (final User user)
throws SQLException
{
execute(new Operation () {
public void invoke () throws SQLException
{
_utable.delete(user);
}
});
}
/**
* Prunes any expired sessions from the sessions table.
*/
public void pruneSessions ()
throws SQLException
{
execute(new Operation () {
public void invoke () throws SQLException
{
_session.execute("delete from sessions where " +
"expires >= CURRENT_DATE()");
}
});
}
public static void main (String[] args)
{
Properties props = new Properties();
props.put("driver", "org.gjt.mm.mysql.Driver");
props.put("url", "jdbc:mysql://localhost:3306/samskivert");
props.put("username", "www");
props.put("password", "Il0ve2PL@Y");
try {
UserRepository rep = new UserRepository(props);
System.out.println(rep.getUser("mdb"));
System.out.println(rep.getUserBySession("auth"));
rep.createUser("samskivert", "foobar", "Michael Bayne",
"mdb@samskivert.com");
rep.createUser("mdb", "foobar", "Michael Bayne",
"mdb@samskivert.com");
rep.shutdown();
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
protected Table _utable;
protected static final int MINIMUM_USERNAME_LENGTH = 2;
/** Used to check usernames for invalid characteres. */
protected static RE _userre;
static {
try {
_userre = new RE("^[_A-Za-z0-9]+$");
} catch (RESyntaxException rese) {
Log.warning("Unable to initialize user regexp?! " + rese);
}
}
}