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:
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user