Author: ajaquith
Date: Thu May 22 21:04:20 2008
New Revision: 659406
URL: http://svn.apache.org/viewvc?rev=659406&view=rev
Log:
Checked in three new features for UserProfile and the UserDatabase classes.
First, profiles can now store arbitrary Serializable objects via a new method
in UserProfile, getAttributes, that returns a Map<Serializable,Serializable>
that can be directly manipulated. Arbitrary attributes such as user preferences
can be added to the profile and be guaranteed to be persisted on save. Second,
the UserProfile now has two methods setLockExpiry(Date)/getLockExpiry that
allow callers to disable user profiles. These are NOT enforced in
AuthenticationManager yet. Third, user profile now have a 'uid' field that
stores a long value for uniquely identifying users. Existing profiles without
UIDs are automatically upgraded when they are loaded by a findBy___() method.
The default XML/JDBC UserDatabase implementations have been enhanced to support
all of these new features. If you have custom UserDatabase implementations, you
should take a look at the new code.
Modified:
incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/AbstractUserDatabase.java
incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/DefaultUserProfile.java
incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/JDBCUserDatabase.java
incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/UserDatabase.java
incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/UserProfile.java
incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/XMLUserDatabase.java
Modified:
incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/AbstractUserDatabase.java
URL:
http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/AbstractUserDatabase.java?rev=659406&r1=659405&r2=659406&view=diff
==============================================================================
---
incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/AbstractUserDatabase.java
(original)
+++
incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/AbstractUserDatabase.java
Thu May 22 21:04:20 2008
@@ -20,12 +20,11 @@
*/
package com.ecyrd.jspwiki.auth.user;
-import java.io.UnsupportedEncodingException;
+import java.io.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
-import java.util.ArrayList;
-import java.util.Properties;
+import java.util.*;
import org.apache.catalina.util.HexUtils;
import org.apache.log4j.Logger;
@@ -49,6 +48,7 @@
protected static final Logger log = Logger.getLogger(
AbstractUserDatabase.class );
protected static final String SHA_PREFIX = "{SHA}";
protected static final String SSHA_PREFIX = "{SSHA}";
+ protected static final long UID_NOT_SET = 0;
/**
* No-op method that in previous versions of JSPWiki was intended to
@@ -183,12 +183,12 @@
public abstract void initialize( WikiEngine engine, Properties props )
throws NoRequiredPropertyException;
/**
- * Factory method that instantiates a new DefaultUserProfile.
- * @see com.ecyrd.jspwiki.auth.user.UserDatabase#newProfile()
+ * Factory method that instantiates a new DefaultUserProfile with a new,
distinct
+ * unique identifier.
*/
public UserProfile newProfile()
{
- return new DefaultUserProfile();
+ return DefaultUserProfile.newProfile( this );
}
/**
@@ -262,6 +262,30 @@
}
/**
+ * Generates a new random user identifier (uid) that is guaranteed to be
unique.
+ * @return
+ */
+ protected static long generateUid( UserDatabase db )
+ {
+ // Keep generating UUIDs until we find one that doesn't collide
+ long uid;
+ boolean collision;
+ do {
+ uid = UUID.randomUUID().getLeastSignificantBits();
+ collision = true;
+ try
+ {
+ db.findByUid( uid );
+ }
+ catch ( NoSuchPrincipalException e )
+ {
+ collision = false;
+ }
+ } while ( collision || uid == UID_NOT_SET );
+ return uid;
+ }
+
+ /**
* Private method that calculates the salted SHA-1 hash of a given
* <code>String</code>. Note that as of JSPWiki 2.8, this method calculates
* a <em>salted</em> hash rather than a plain hash.
@@ -312,4 +336,25 @@
return hash;
}
+ /**
+ * Parses a long integer from a supplied string, or returns 0 if not
parsable.
+ * @param value the string to parse
+ * @return the value parsed
+ */
+ protected long parseLong( String value )
+ {
+ if ( value == null || value.length() == 0 )
+ {
+ return 0;
+ }
+ try
+ {
+ return Long.parseLong( value );
+ }
+ catch ( NumberFormatException e )
+ {
+ return 0;
+ }
+ }
+
}
Modified:
incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/DefaultUserProfile.java
URL:
http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/DefaultUserProfile.java?rev=659406&r1=659405&r2=659406&view=diff
==============================================================================
---
incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/DefaultUserProfile.java
(original)
+++
incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/DefaultUserProfile.java
Thu May 22 21:04:20 2008
@@ -20,7 +20,9 @@
*/
package com.ecyrd.jspwiki.auth.user;
-import java.util.Date;
+.import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
/**
* Default implementation for representing wiki user information, such as the
@@ -34,21 +36,30 @@
private static final String EMPTY_STRING = "";
private static final String WHITESPACE = "\\s";
+
+ private Map<Serializable,Serializable> m_attributes = new
HashMap<Serializable,Serializable>();
private Date m_created = null;
private String m_email = null;
private String m_fullname = null;
+
+ private Date m_lockExpiry = null;
private String m_loginName = null;
private Date m_modified = null;
private String m_password = null;
+
+ private long m_uid = -1;
private String m_wikiname = null;
-
+.
+ /**
+ * [EMAIL PROTECTED]
+ */
public boolean equals( Object o )
{
if ( ( o != null ) && ( o instanceof UserProfile ) )
@@ -263,4 +274,60 @@
}
return arg1.equals( arg2 );
}
+
+ //--------------------------- Attribute and lock interface implementations
---------------------------
+
+ /**
+ * [EMAIL PROTECTED]
+ */
+ public Map<Serializable,Serializable> getAttributes()
+ {
+ return m_attributes;
+ }
+
+ /**
+ * [EMAIL PROTECTED]
+ */
+ public Date getLockExpiry()
+ {
+ return isLocked() ? m_lockExpiry : null;
+ }
+
+ /**
+ * [EMAIL PROTECTED]
+ */
+ public long getUid()
+ {
+ return m_uid;
+ }
+
+ /**
+ * [EMAIL PROTECTED]
+ */
+ public boolean isLocked()
+ {
+ boolean locked = m_lockExpiry != null && System.currentTimeMillis() <
m_lockExpiry.getTime();
+
+ // Clear the lock if it's expired already
+ if ( !locked && m_lockExpiry != null ){
+ m_lockExpiry = null;
+ }
+ return locked;
+ }
+
+ /**
+ * [EMAIL PROTECTED]
+ */
+ public void setLockExpiry( Date expiry )
+ {
+ m_lockExpiry = expiry;
+ }
+
+ /**
+ * [EMAIL PROTECTED]
+ */
+ public void setUid( long uid )
+ {
+ m_uid = uid;
+ }
}
Modified:
incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/JDBCUserDatabase.java
URL:
http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/JDBCUserDatabase.java?rev=659406&r1=659405&r2=659406&view=diff
==============================================================================
---
incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/JDBCUserDatabase.java
(original)
+++
incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/JDBCUserDatabase.java
Thu May 22 21:04:20 2008
@@ -20,12 +20,11 @@
*/
package com.ecyrd.jspwiki.auth.user;
+import java.io.*;
import java.security.Principal;
import java.sql.*;
+import java.util.*;
import java.util.Date;
-import java.util.HashSet;
-import java.util.Properties;
-import java.util.Set;
import javax.naming.Context;
import javax.naming.InitialContext;
@@ -37,6 +36,7 @@
import com.ecyrd.jspwiki.auth.NoSuchPrincipalException;
import com.ecyrd.jspwiki.auth.WikiPrincipal;
import com.ecyrd.jspwiki.auth.WikiSecurityException;
+import com.ecyrd.jspwiki.util.Serializer;
/**
* <p>
@@ -66,6 +66,11 @@
* <td>The table that stores the user profiles</td>
* </tr>
* <tr>
+ * <td><code>jspwiki.userdatabase.attributes</code></td>
+ * <td><code>attributes</code></td>
+ * <td>The CLOB column containing the profile's custom attributes, stored as
key/value strings, each separated by newline.</td>
+ * </tr>
+ * <tr>
* <td><code>jspwiki.userdatabase.created</code></td>
* <td><code>created</code></td>
* <td>The column containing the profile's creation timestamp</td>
@@ -96,11 +101,21 @@
* <td>The column containing the profile's last-modified timestamp</td>
* </tr>
* <tr>
+ * <td><code>jspwiki.userdatabase.uid</code></td>
+ * <td><code>uid</code></td>
+ * <td>The column containing the profile's unique identifier, as a long
integer</td>
+ * </tr>
+ * <tr>
* <td><code>jspwiki.userdatabase.wikiName</code></td>
* <td><code>wiki_name</code></td>
* <td>The column containing the user's wiki name</td>
* </tr>
* <tr>
+ * <td><code>jspwiki.userdatabase.lockExpiry</code></td>
+ * <td><code>lock_expiry</code></td>
+ * <td>The column containing the date/time when the profile, if locked, should
be unlocked.</td>
+ * </tr>
+ * <tr>
* <td><code>jspwiki.userdatabase.roleTable</code></td>
* <td><code>roles</code></td>
* <td>The table that stores user roles. When a new user is created, a new
@@ -163,16 +178,18 @@
private static final String NOTHING = "";
+ public static final String DEFAULT_DB_ATTRIBUTES = "attributes";
+
public static final String DEFAULT_DB_CREATED = "created";
public static final String DEFAULT_DB_EMAIL = "email";
public static final String DEFAULT_DB_FULL_NAME = "full_name";
- public static final String DEFAULT_DB_HASH_PREFIX = "true";
-
public static final String DEFAULT_DB_JNDI_NAME = "jdbc/UserDatabase";
+ public static final String DEFAULT_DB_LOCK_EXPIRY = "lock_expiry";
+
public static final String DEFAULT_DB_MODIFIED = "modified";
public static final String DEFAULT_DB_ROLE = "role";
@@ -185,8 +202,12 @@
public static final String DEFAULT_DB_PASSWORD = "password";
+ public static final String DEFAULT_DB_UID = "uid";
+
public static final String DEFAULT_DB_WIKI_NAME = "wiki_name";
+ public static final String PROP_DB_ATTRIBUTES =
"jspwiki.userdatabase.attributes";
+
public static final String PROP_DB_CREATED =
"jspwiki.userdatabase.created";
public static final String PROP_DB_EMAIL = "jspwiki.userdatabase.email";
@@ -195,7 +216,7 @@
public static final String PROP_DB_DATASOURCE =
"jspwiki.userdatabase.datasource";
- public static final String PROP_DB_HASH_PREFIX =
"jspwiki.userdatabase.hashPrefix";
+ public static final String PROP_DB_LOCK_EXPIRY =
"jspwiki.userdatabase.lockExpiry";
public static final String PROP_DB_LOGIN_NAME =
"jspwiki.userdatabase.loginName";
@@ -203,6 +224,8 @@
public static final String PROP_DB_PASSWORD =
"jspwiki.userdatabase.password";
+ public static final String PROP_DB_UID = "jspwiki.userdatabase.uid";
+
public static final String PROP_DB_ROLE = "jspwiki.userdatabase.role";
public static final String PROP_DB_ROLE_TABLE =
"jspwiki.userdatabase.roleTable";
@@ -223,6 +246,8 @@
private String m_findByLoginName = null;
+ private String m_findByUid = null;
+
private String m_findByWikiName = null;
private String m_renameProfile = null;
@@ -243,10 +268,14 @@
private String m_userTable = null;
+ private String m_attributes = null;
+
private String m_email = null;
private String m_fullName = null;
+ private String m_lockExpiry = null;
+
private String m_loginName = null;
private String m_password = null;
@@ -255,6 +284,8 @@
private String m_roleTable = null;
+ private String m_uid = null;
+
private String m_wikiName = null;
private String m_created = null;
@@ -351,6 +382,14 @@
/**
* @see com.ecyrd.jspwiki.auth.user.UserDatabase#findByWikiName(String)
*/
+ public UserProfile findByUid( long uid ) throws NoSuchPrincipalException
+ {
+ return findByPreparedStatement( m_findByUid, Long.valueOf( uid ) );
+ }
+
+ /**
+ * @see com.ecyrd.jspwiki.auth.user.UserDatabase#findByWikiName(String)
+ */
public UserProfile findByWikiName( String index ) throws
NoSuchPrincipalException
{
return findByPreparedStatement( m_findByWikiName, index );
@@ -422,23 +461,47 @@
m_userTable = props.getProperty( PROP_DB_TABLE, DEFAULT_DB_TABLE );
m_email = props.getProperty( PROP_DB_EMAIL, DEFAULT_DB_EMAIL );
m_fullName = props.getProperty( PROP_DB_FULL_NAME,
DEFAULT_DB_FULL_NAME );
+ m_lockExpiry = props.getProperty( PROP_DB_LOCK_EXPIRY,
DEFAULT_DB_LOCK_EXPIRY );
m_loginName = props.getProperty( PROP_DB_LOGIN_NAME,
DEFAULT_DB_LOGIN_NAME );
m_password = props.getProperty( PROP_DB_PASSWORD,
DEFAULT_DB_PASSWORD );
+ m_uid = props.getProperty( PROP_DB_UID, DEFAULT_DB_UID );
m_wikiName = props.getProperty( PROP_DB_WIKI_NAME,
DEFAULT_DB_WIKI_NAME );
m_created = props.getProperty( PROP_DB_CREATED, DEFAULT_DB_CREATED
);
m_modified = props.getProperty( PROP_DB_MODIFIED,
DEFAULT_DB_MODIFIED );
+ m_attributes = props.getProperty( PROP_DB_ATTRIBUTES,
DEFAULT_DB_ATTRIBUTES );
m_findAll = "SELECT * FROM " + m_userTable;
m_findByEmail = "SELECT * FROM " + m_userTable + " WHERE " +
m_email + "=?";
m_findByFullName = "SELECT * FROM " + m_userTable + " WHERE " +
m_fullName + "=?";
m_findByLoginName = "SELECT * FROM " + m_userTable + " WHERE " +
m_loginName + "=?";
+ m_findByUid = "SELECT * FROM " + m_userTable + " WHERE " + m_uid +
"=?";
m_findByWikiName = "SELECT * FROM " + m_userTable + " WHERE " +
m_wikiName + "=?";
- // Prepare the user isert/update SQL
- m_insertProfile = "INSERT INTO " + m_userTable + " (" + m_email +
"," + m_fullName + "," + m_password + ","
- + m_wikiName + "," + m_modified + "," +
m_loginName + "," + m_created + ") VALUES (?,?,?,?,?,?,?)";
- m_updateProfile = "UPDATE " + m_userTable + " SET " + m_email +
"=?," + m_fullName + "=?," + m_password + "=?,"
- + m_wikiName + "=?," + m_modified + "=? WHERE "
+ m_loginName + "=?";
+ // The user insert SQL prepared statement
+ m_insertProfile = "INSERT INTO " + m_userTable + " ("
+ + m_uid + ","
+ + m_email + ","
+ + m_fullName + ","
+ + m_password + ","
+ + m_wikiName + ","
+ + m_modified + ","
+ + m_loginName + ","
+ + m_attributes + ","
+ + m_created
+ + ") VALUES (?,?,?,?,?,?,?,?,?)";
+
+ // The user update SQL prepared statement
+ m_updateProfile = "UPDATE " + m_userTable + " SET "
+ + m_uid + "=?,"
+ + m_email + "=?,"
+ + m_fullName + "=?,"
+ + m_password + "=?,"
+ + m_wikiName + "=?,"
+ + m_modified + "=?,"
+ + m_loginName + "=?,"
+ + m_attributes + "=?,"
+ + m_lockExpiry + "=? "
+ + "WHERE " + m_loginName + "=?";
// Prepare the role insert SQL
m_roleTable = props.getProperty( PROP_DB_ROLE_TABLE,
DEFAULT_DB_ROLE_TABLE );
@@ -647,17 +710,27 @@
Timestamp ts = new Timestamp( System.currentTimeMillis() );
Date modDate = new Date( ts.getTime() );
+ java.sql.Date lockExpiry = profile.getLockExpiry() == null ? null
: new java.sql.Date( profile.getLockExpiry().getTime() );
if( existingProfile == null )
{
// User is new: insert new user record
ps = conn.prepareStatement( m_insertProfile );
- ps.setString( 1, profile.getEmail() );
- ps.setString( 2, profile.getFullname() );
- ps.setString( 3, password );
- ps.setString( 4, profile.getWikiName() );
- ps.setTimestamp( 5, ts );
- ps.setString( 6, profile.getLoginName() );
- ps.setTimestamp( 7, ts );
+ ps.setLong( 1, profile.getUid() );
+ ps.setString( 2, profile.getEmail() );
+ ps.setString( 3, profile.getFullname() );
+ ps.setString( 4, password );
+ ps.setString( 5, profile.getWikiName() );
+ ps.setTimestamp( 6, ts );
+ ps.setString( 7, profile.getLoginName() );
+ try
+ {
+ ps.setString( 8, Serializer.serializeToBase64(
profile.getAttributes() ) );
+ }
+ catch ( IOException e )
+ {
+ throw new WikiSecurityException( "Could not save user
profile attribute. Reason: " + e.getMessage() );
+ }
+ ps.setTimestamp( 9, ts );
ps.execute();
ps.close();
@@ -687,12 +760,23 @@
{
// User exists: modify existing record
ps = conn.prepareStatement( m_updateProfile );
- ps.setString( 1, profile.getEmail() );
- ps.setString( 2, profile.getFullname() );
- ps.setString( 3, password );
- ps.setString( 4, profile.getWikiName() );
- ps.setTimestamp( 5, ts );
- ps.setString( 6, profile.getLoginName() );
+ ps.setLong( 1, profile.getUid() );
+ ps.setString( 2, profile.getEmail() );
+ ps.setString( 3, profile.getFullname() );
+ ps.setString( 4, password );
+ ps.setString( 5, profile.getWikiName() );
+ ps.setTimestamp( 6, ts );
+ ps.setString( 7, profile.getLoginName() );
+ try
+ {
+ ps.setString( 8, Serializer.serializeToBase64(
profile.getAttributes() ) );
+ }
+ catch ( IOException e )
+ {
+ throw new WikiSecurityException( "Could not save user
profile attribute. Reason: " + e.getMessage() );
+ }
+ ps.setDate( 9, lockExpiry );
+ ps.setString( 10, profile.getLoginName() );
ps.execute();
ps.close();
}
@@ -722,11 +806,15 @@
}
/**
- * @param rs
- * @return
+ * Private method that returns the first [EMAIL PROTECTED] UserProfile}
matching a
+ * named column's value. This method will also set the UID if it has not
yet been set.
+ * @param sql the SQL statement that should be prepared; it must have one
parameter
+ * to set (either a String or a Long)
+ * @param index the value to match
+ * @return the resolved UserProfile
* @throws SQLException
*/
- private UserProfile findByPreparedStatement( String sql, String index )
throws NoSuchPrincipalException
+ private UserProfile findByPreparedStatement( String sql, Object index )
throws NoSuchPrincipalException
{
UserProfile profile = null;
boolean found = false;
@@ -742,7 +830,21 @@
}
PreparedStatement ps = conn.prepareStatement( sql );
- ps.setString( 1, index );
+
+ // Set the parameter to search by
+ if ( index instanceof String )
+ {
+ ps.setString( 1, (String)index );
+ }
+ else if ( index instanceof Long )
+ {
+ ps.setLong( 1, ( (Long)index).longValue() );
+ }
+ else {
+ throw new IllegalArgumentException( "Index type not
recognized!" );
+ }
+
+ // Go and get the record!
ResultSet rs = ps.executeQuery();
while ( rs.next() )
{
@@ -751,13 +853,37 @@
unique = false;
break;
}
- profile = new DefaultUserProfile();
+ profile = newProfile();
+
+ // Fetch the basic user attributes
+ profile.setUid( rs.getLong( m_uid ) );
+ if ( profile.getUid() == UID_NOT_SET )
+ {
+ profile.setUid( generateUid( this ) );
+ }
profile.setCreated( rs.getTimestamp( m_created ) );
profile.setEmail( rs.getString( m_email ) );
profile.setFullname( rs.getString( m_fullName ) );
profile.setLastModified( rs.getTimestamp( m_modified ) );
+ Date lockExpiry = rs.getDate( m_lockExpiry );
+ profile.setLockExpiry( rs.wasNull() ? null : lockExpiry );
profile.setLoginName( rs.getString( m_loginName ) );
profile.setPassword( rs.getString( m_password ) );
+
+ // Fetch the user attributes
+ String rawAttributes = rs.getString( m_attributes );
+ if ( rawAttributes != null )
+ {
+ try
+ {
+ Map<? extends Serializable,? extends Serializable>
attributes = Serializer.deserializeFromBase64( rawAttributes );
+ profile.getAttributes().putAll( attributes );
+ }
+ catch ( IOException e )
+ {
+ log.error( "Could not parse user profile attributes!",
e );
+ }
+ }
found = true;
}
ps.close();
Modified:
incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/UserDatabase.java
URL:
http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/UserDatabase.java?rev=659406&r1=659405&r2=659406&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/UserDatabase.java
(original)
+++ incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/UserDatabase.java
Thu May 22 21:04:20 2008
@@ -127,6 +127,17 @@
/**
* Looks up and returns the first [EMAIL PROTECTED] UserProfile} in the
user database
+ * that matches a profile having a given unique ID (uid). If the user
database
+ * does not contain a user with a unique ID, it throws a
+ * [EMAIL PROTECTED] NoSuchPrincipalException}.
+ * @param uid the unique identifier of the desired user profile
+ * @return the user profile
+ * @since 2.8
+ */
+ public UserProfile findByUid( long uid ) throws NoSuchPrincipalException;
+
+ /**
+ * Looks up and returns the first [EMAIL PROTECTED] UserProfile} in the
user database
* that matches a profile having a given wiki name. If the user database
* does not contain a user with a matching attribute, throws a
* [EMAIL PROTECTED] NoSuchPrincipalException}.
Modified:
incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/UserProfile.java
URL:
http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/UserProfile.java?rev=659406&r1=659405&r2=659406&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/UserProfile.java
(original)
+++ incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/UserProfile.java
Thu May 22 21:04:20 2008
@@ -20,12 +20,17 @@
*/
package com.ecyrd.jspwiki.auth.user;
+import java.io.Serializable;
import java.util.Date;
+import java.util.Map;
/**
* Class for representing wiki user information, such as the login name, full
* name, wiki name, and e-mail address. Note that since 2.6 the wiki name is
* required to be automatically computed from the full name.
+ * As of 2.8, user profiles can store custom key/value String attributes, and
store
+ * a unique ID. Locks are checked by [EMAIL PROTECTED]
com.ecyrd.jspwiki.auth.AuthenticationManager};
+ * if a profile is locked, the user cannot log with that profile.
* @author Andrew Jaquith
* @since 2.3
*/
@@ -33,6 +38,14 @@
{
/**
+ * Returns the attributes associated with this profile as a Map of
key/value pairs.
+ * The Map should generally be a "live" Map; changes to the keys or values
will be reflected
+ * in the UserProfile.
+ * @return the attributes
+ */
+ public Map<Serializable,Serializable> getAttributes();
+
+ /**
* Returns the creation date.
* @return the creation date
*/
@@ -57,6 +70,18 @@
public Date getLastModified();
/**
+ * Returns the date/time of expiration of the profile's lock, if it has
been
+ * previously locked via [EMAIL PROTECTED] #setLockExpiry(Date)} and the
lock is
+ * still active. If the profile is unlocked, this method returns
<code>null</code>.
+ * Note that calling this method after the expiration date, <em>even if
had previously
+ * been set explicitly by [EMAIL PROTECTED] #setLockExpiry(Date)}</em>,
will always return
+ * <code>null</null>.
+ *
+ * @return the lock expiration date
+ */
+ public Date getLockExpiry();
+
+ /**
* Returns the user's login name.
* @return the login name
*/
@@ -74,6 +99,13 @@
public String getPassword();
/**
+ * Returns the unique identifier for the user profile. If not previously
+ * set, the value will be -1.
+ * @return the unique ID.
+ */
+ public long getUid();
+
+ /**
* Returns the user's wiki name, based on the full name with all
* whitespace removed.
* @return the wiki name.
@@ -81,6 +113,15 @@
public String getWikiName();
/**
+ * Returns
+ * <code>true</code> if the profile is currently locked (disabled);
<code>false</code> otherwise.
+ * By default, profiles are created unlocked. Strictly speaking, calling
this method is equivalent to calling [EMAIL PROTECTED] #getLockExpiry()}
+ * and, if it returns a non-<code>null</code> value, checking if the date
returned is later than the current time.
+ * @return the result
+ */
+ public boolean isLocked();
+
+ /**
* Returns <code>true</code> if the profile has never been
* saved before. Implementing classes might check the
* last modified date, for example, to determine this.
@@ -113,6 +154,14 @@
public void setLastModified( Date date );
/**
+ * Locks the profile until a specified lock expiration date.
+ *
+ * @param expiry the date the lock expires; setting this value to
<code>null</code>
+ * will cause the lock to be cleared.
+ */
+ public void setLockExpiry( Date expiry );
+
+ /**
* Sets the name by which the user logs in. The login name is used as the
* username for custom authentication (see
* [EMAIL PROTECTED]
com.ecyrd.jspwiki.auth.AuthenticationManager#login(WikiSession, String,
String)},
@@ -136,6 +185,12 @@
public void setPassword( String arg );
/**
+ * Sets the unique identifier for the user profile. Note that UserDatabase
implementations
+ * are required <em>not</em> to change the unique identifier after the
initial save.
+ */
+ public void setUid( long uid );
+
+ /**
* No-op method. In previous versions of JSPWiki, the method
* set the user's wiki name directly. Now, the wiki name is automatically
* calculated based on the full name.
Modified:
incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/XMLUserDatabase.java
URL:
http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/XMLUserDatabase.java?rev=659406&r1=659405&r2=659406&view=diff
==============================================================================
---
incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/XMLUserDatabase.java
(original)
+++
incubator/jspwiki/trunk/src/com/ecyrd/jspwiki/auth/user/XMLUserDatabase.java
Thu May 22 21:04:20 2008
@@ -30,9 +30,7 @@
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NodeList;
+import org.w3c.dom.*;
import org.xml.sax.SAXException;
import com.ecyrd.jspwiki.NoRequiredPropertyException;
@@ -40,6 +38,7 @@
import com.ecyrd.jspwiki.auth.NoSuchPrincipalException;
import com.ecyrd.jspwiki.auth.WikiPrincipal;
import com.ecyrd.jspwiki.auth.WikiSecurityException;
+import com.ecyrd.jspwiki.util.Serializer;
/**
* <p>Manages [EMAIL PROTECTED] DefaultUserProfile} objects using XML files
for persistence.
@@ -71,6 +70,8 @@
private static final String DEFAULT_USERDATABASE = "userdatabase.xml";
+ private static final String ATTRIBUTES_TAG = "attributes";
+
private static final String CREATED = "created";
private static final String EMAIL = "email";
@@ -81,8 +82,12 @@
private static final String LAST_MODIFIED = "lastModified";
+ private static final String LOCK_EXPIRY = "lockExpiry";
+
private static final String PASSWORD = "password";
+ private static final String UID = "uid";
+
private static final String USER_TAG = "user";
private static final String WIKI_NAME = "wikiName";
@@ -183,6 +188,19 @@
}
/**
+ * [EMAIL PROTECTED]
+ */
+ public UserProfile findByUid( long uid ) throws NoSuchPrincipalException
+ {
+ UserProfile profile = findByAttribute( UID, Long.toString( uid ) );
+ if ( profile != null )
+ {
+ return profile;
+ }
+ throw new NoSuchPrincipalException( "Not in database: " + uid );
+ }
+
+ /**
* Looks up and returns the first [EMAIL PROTECTED] UserProfile}in the
user database
* that matches a profile having a given wiki name. If the user database
* does not contain a user with a matching attribute, throws a
@@ -231,7 +249,7 @@
principals.add( principal );
}
}
- return (Principal[])principals.toArray( new
Principal[principals.size()] );
+ return principals.toArray( new Principal[principals.size()] );
}
/**
@@ -344,7 +362,9 @@
for( int i = 0; i < nodes.getLength(); i++ )
{
Element user = (Element)nodes.item( i );
- io.write( "<" + USER_TAG + " ");
+ io.write( " <" + USER_TAG + " ");
+ io.write( UID );
+ io.write( "=\"" + user.getAttribute( UID ) + "\" " );
io.write( LOGIN_NAME );
io.write( "=\"" + user.getAttribute( LOGIN_NAME ) + "\" " );
io.write( WIKI_NAME );
@@ -359,7 +379,19 @@
io.write( "=\"" + user.getAttribute( CREATED ) + "\" " );
io.write( LAST_MODIFIED );
io.write( "=\"" + user.getAttribute( LAST_MODIFIED ) + "\" " );
- io.write(" />\n");
+ io.write( LOCK_EXPIRY );
+ io.write( "=\"" + user.getAttribute( LOCK_EXPIRY ) + "\" " );
+ io.write( ">" );
+ NodeList attributes = user.getElementsByTagName(
ATTRIBUTES_TAG );
+ for ( int j = 0; j < attributes.getLength(); j++ )
+ {
+ Element attribute = (Element)attributes.item( j );
+ String value = extractText( attribute );
+ io.write( "\n <" + ATTRIBUTES_TAG + ">" );
+ io.write( value );
+ io.write( "</" + ATTRIBUTES_TAG + ">" );
+ }
+ io.write("\n </" +USER_TAG + ">\n");
}
io.write("</users>");
io.close();
@@ -495,17 +527,29 @@
Date modDate = new Date( System.currentTimeMillis() );
if ( isNew )
{
+ // Create new user node
profile.setCreated( modDate );
log.info( "Creating new user " + index );
user = c_dom.createElement( USER_TAG );
c_dom.getDocumentElement().appendChild( user );
setAttribute( user, CREATED, c_format.format( profile.getCreated()
) );
}
+ else
+ {
+ // To update existing user node, delete old attributes first...
+ NodeList artributes = user.getElementsByTagName( ATTRIBUTES_TAG );
+ for ( int i = 0; i < artributes.getLength(); i++ )
+ {
+ user.removeChild( artributes.item( i ) );
+ }
+ }
setAttribute( user, LAST_MODIFIED, c_format.format( modDate ) );
setAttribute( user, LOGIN_NAME, profile.getLoginName() );
setAttribute( user, FULL_NAME, profile.getFullname() );
setAttribute( user, WIKI_NAME, profile.getWikiName() );
setAttribute( user, EMAIL, profile.getEmail() );
+ Date lockExpiry = profile.getLockExpiry();
+ setAttribute( user, LOCK_EXPIRY, lockExpiry == null ? "" :
c_format.format( lockExpiry ) );
// Hash and save the new password if it's different from old one
String newPassword = profile.getPassword();
@@ -517,6 +561,23 @@
setAttribute( user, PASSWORD, getHash( newPassword ) );
}
}
+
+ // Save the attributes as as Base64 string
+ if ( profile.getAttributes().size() > 0 )
+ {
+ try
+ {
+ String encodedAttributes = Serializer.serializeToBase64(
profile.getAttributes() );
+ Element attributes = c_dom.createElement( ATTRIBUTES_TAG );
+ user.appendChild( attributes );
+ Text value = c_dom.createTextNode( encodedAttributes );
+ attributes.appendChild( value );
+ }
+ catch ( IOException e )
+ {
+ throw new WikiSecurityException( "Could not save user profile
attribute. Reason: " + e.getMessage() );
+ }
+ }
// Set the profile timestamps
if ( isNew )
@@ -531,7 +592,8 @@
/**
* Private method that returns the first [EMAIL PROTECTED]
UserProfile}matching a
- * <user> element's supplied attribute.
+ * <user> element's supplied attribute. This method will also
+ * set the UID if it has not yet been set.
* @param matchAttribute
* @param index
* @return the profile, or <code>null</code> if not found
@@ -551,16 +613,52 @@
Element user = (Element) users.item( i );
if ( user.getAttribute( matchAttribute ).equals( index ) )
{
- UserProfile profile = new DefaultUserProfile();
+ UserProfile profile = newProfile();
+
+ // Parse basic attributes
+ profile.setUid( parseLong( user.getAttribute( UID ) ) );
+ if ( profile.getUid() == UID_NOT_SET )
+ {
+ profile.setUid( generateUid( this ) );
+ }
profile.setLoginName( user.getAttribute( LOGIN_NAME ) );
profile.setFullname( user.getAttribute( FULL_NAME ) );
profile.setPassword( user.getAttribute( PASSWORD ) );
profile.setEmail( user.getAttribute( EMAIL ) );
+
+ // Get created/modified timestamps
String created = user.getAttribute( CREATED );
String modified = user.getAttribute( LAST_MODIFIED );
-
profile.setCreated( parseDate( profile, created ) );
profile.setLastModified( parseDate( profile, modified ) );
+
+ // Is the profile locked?
+ String lockExpiry = user.getAttribute( LOCK_EXPIRY );
+ if ( lockExpiry == null || lockExpiry.length() == 0 )
+ {
+ profile.setLockExpiry( null );
+ }
+ else
+ {
+ profile.setLockExpiry( new Date( Long.parseLong(
lockExpiry ) ) );
+ }
+
+ // Extract all of the user's attributes (should only be one
attributes tag, but you never know!)
+ NodeList attributes = user.getElementsByTagName(
ATTRIBUTES_TAG );
+ for ( int j = 0; j < attributes.getLength(); j++ )
+ {
+ Element attribute = (Element)attributes.item( j );
+ String serializedMap = extractText( attribute );
+ try
+ {
+ Map<? extends Serializable,? extends Serializable> map
= Serializer.deserializeFromBase64( serializedMap );
+ profile.getAttributes().putAll( map );
+ }
+ catch ( IOException e )
+ {
+ log.error( "Could not parse user profile attributes!",
e );
+ }
+ }
return profile;
}
@@ -569,6 +667,29 @@
}
/**
+ * Extracts all of the text nodes that are immediate children of an
Element.
+ * @param element the base element
+ * @return the text nodes that are immediate children of the base element,
concatenated together
+ */
+ private String extractText( Element element )
+ {
+ String text = "";
+ if ( element.getChildNodes().getLength() > 0 )
+ {
+ NodeList children = element.getChildNodes();
+ for ( int k = 0; k < children.getLength(); k++ )
+ {
+ Node child = children.item( k );
+ if ( child.getNodeType() == Node.TEXT_NODE )
+ {
+ text = text + ((Text)child).getData();
+ }
+ }
+ }
+ return text;
+ }
+
+ /**
* Tries to parse a date using the default format - then, for backwards
* compatibility reasons, tries the platform default.
*
@@ -614,6 +735,16 @@
for( int i = 0; i < users.getLength(); i++ )
{
Element user = (Element) users.item( i );
+
+ // Sanitize UID (and generate a new one if one does not exist)
+ String uid = user.getAttribute( UID ).trim();
+ if ( uid == null || uid.length() == 0 || "-1".equals( uid ) )
+ {
+ uid = String.valueOf( generateUid( this ) );
+ user.setAttribute( UID, uid );
+ }
+
+ // Sanitize dates
String loginName = user.getAttribute( LOGIN_NAME );
String created = user.getAttribute( CREATED );
String modified = user.getAttribute( LAST_MODIFIED );
@@ -645,7 +776,7 @@
}
/**
- * Private method that sets an attibute value for a supplied DOM element.
+ * Private method that sets an attribute value for a supplied DOM element.
* @param element the element whose attribute is to be set
* @param attribute the name of the attribute to set
* @param value the desired attribute value