You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jspwiki.apache.org by aj...@apache.org on 2008/08/03 14:17:35 UTC

svn commit: r682144 [3/4] - in /incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki: auth/authorize/ auth/login/ auth/permissions/ auth/user/ content/

Modified: incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/AbstractUserDatabase.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/AbstractUserDatabase.java?rev=682144&r1=682143&r2=682144&view=diff
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/AbstractUserDatabase.java (original)
+++ incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/AbstractUserDatabase.java Sun Aug  3 05:17:34 2008
@@ -1,30 +1,30 @@
-/*
+/* 
     JSPWiki - a JSP-based WikiWiki clone.
 
-    Copyright (C) 2001-2007 Janne Jalkanen (Janne.Jalkanen@iki.fi)
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU Lesser General Public License as published by
-    the Free Software Foundation; either version 2.1 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU Lesser General Public License for more details.
-
-    You should have received a copy of the GNU Lesser General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.  
  */
 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;
@@ -34,6 +34,7 @@
 import com.ecyrd.jspwiki.auth.NoSuchPrincipalException;
 import com.ecyrd.jspwiki.auth.WikiPrincipal;
 import com.ecyrd.jspwiki.auth.WikiSecurityException;
+import com.ecyrd.jspwiki.util.CryptoUtil;
 
 /**
  * Abstract UserDatabase class that provides convenience methods for finding
@@ -46,8 +47,8 @@
 
     protected static final Logger log = Logger.getLogger( AbstractUserDatabase.class );
     protected static final String SHA_PREFIX = "{SHA}";
-    protected static final String  PROP_SHARED_WITH_CONTAINER = "jspwiki.userdatabase.isSharedWithContainer";
-
+    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
@@ -58,6 +59,7 @@
      * @deprecated there is no need to call this method because the save, rename and
      * delete methods contain their own commit logic
      */
+    @SuppressWarnings("deprecation")
     public synchronized void commit() throws WikiSecurityException
     { }
 
@@ -117,21 +119,25 @@
     }
 
     /**
+     * {@inheritDoc}
      * @see com.ecyrd.jspwiki.auth.user.UserDatabase#findByEmail(java.lang.String)
      */
     public abstract UserProfile findByEmail( String index ) throws NoSuchPrincipalException;
 
     /**
+     * {@inheritDoc}
      * @see com.ecyrd.jspwiki.auth.user.UserDatabase#findByFullName(java.lang.String)
      */
     public abstract UserProfile findByFullName( String index ) throws NoSuchPrincipalException;
 
     /**
+     * {@inheritDoc}
      * @see com.ecyrd.jspwiki.auth.user.UserDatabase#findByLoginName(java.lang.String)
      */
     public abstract UserProfile findByLoginName( String index ) throws NoSuchPrincipalException;
 
     /**
+     * {@inheritDoc}
      * @see com.ecyrd.jspwiki.auth.user.UserDatabase#findByWikiName(java.lang.String)
      */
     public abstract UserProfile findByWikiName( String index ) throws NoSuchPrincipalException;
@@ -149,13 +155,14 @@
      *            {@link UserProfile#getLoginName()}method.
      * @return the array of Principals representing the user
      * @see com.ecyrd.jspwiki.auth.user.UserDatabase#getPrincipals(java.lang.String)
+     * @throws NoSuchPrincipalException {@inheritDoc}
      */
     public Principal[] getPrincipals( String identifier ) throws NoSuchPrincipalException
     {
         try
         {
             UserProfile profile = findByLoginName( identifier );
-            ArrayList principals = new ArrayList();
+            ArrayList<Principal> principals = new ArrayList<Principal>();
             if ( profile.getLoginName() != null && profile.getLoginName().length() > 0 )
             {
                 principals.add( new WikiPrincipal( profile.getLoginName(), WikiPrincipal.LOGIN_NAME ) );
@@ -168,7 +175,7 @@
             {
                 principals.add( new WikiPrincipal( profile.getWikiName(), WikiPrincipal.WIKI_NAME ) );
             }
-            return (Principal[]) principals.toArray( new Principal[principals.size()] );
+            return principals.toArray( new Principal[principals.size()] );
         }
         catch( NoSuchPrincipalException e )
         {
@@ -177,20 +184,24 @@
     }
 
     /**
+     * {@inheritDoc}
      * @see com.ecyrd.jspwiki.auth.user.UserDatabase#initialize(com.ecyrd.jspwiki.WikiEngine, java.util.Properties)
      */
     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.
+     * 
+     * @return A new, empty profile.
      */
     public UserProfile newProfile()
     {
-        return new DefaultUserProfile();
+        return DefaultUserProfile.newProfile( this );
     }
 
     /**
+     * {@inheritDoc}
      * @see com.ecyrd.jspwiki.auth.user.UserDatabase#save(com.ecyrd.jspwiki.auth.user.UserProfile)
      */
     public abstract void save( UserProfile profile ) throws WikiSecurityException;
@@ -206,26 +217,117 @@
      * @param password the user's password (obtained from user input, e.g., a web form)
      * @return <code>true</code> if the supplied user password matches the
      * stored password
+     * @throws NoSuchAlgorithmException 
      * @see com.ecyrd.jspwiki.auth.user.UserDatabase#validatePassword(java.lang.String,
      *      java.lang.String)
      */
     public boolean validatePassword( String loginName, String password )
     {
-        String hashedPassword = getHash( password );
+        String hashedPassword;
         try
         {
             UserProfile profile = findByLoginName( loginName );
             String storedPassword = profile.getPassword();
+            
+            // Is the password stored as a salted hash (the new 2.8 format?)
+            boolean newPasswordFormat = storedPassword.startsWith( SSHA_PREFIX );
+            
+            // If new format, verify the hash
+            if ( newPasswordFormat )
+            {
+                hashedPassword = getHash( password );
+                return CryptoUtil.verifySaltedPassword( password.getBytes("UTF-8"), storedPassword );
+            }
+
+            // If old format, verify using the old SHA verification algorithm
             if ( storedPassword.startsWith( SHA_PREFIX ) )
             {
                 storedPassword = storedPassword.substring( SHA_PREFIX.length() );
             }
-            return hashedPassword.equals( storedPassword );
+            hashedPassword = getOldHash( password );
+            boolean verified = hashedPassword.equals( storedPassword ); 
+            
+            // If in the old format and password verified, upgrade the hash to SSHA
+            if ( verified )
+            {
+                profile.setPassword( password );
+                save( profile );
+            }
+            
+            return verified;
         }
         catch( NoSuchPrincipalException e )
         {
-            return false;
         }
+        catch( NoSuchAlgorithmException e )
+        {
+            log.error( "Unsupported algorithm: " + e.getMessage() );
+        }
+        catch( UnsupportedEncodingException e )
+        {
+            log.fatal( "You do not have UTF-8!?!" );
+        }
+        catch( WikiSecurityException e )
+        {
+            log.error( "Could not upgrade SHA password to SSHA because profile could not be saved. Reason: " + e.getMessage() );
+            e.printStackTrace();
+        }
+        return false;
+    }
+
+    /**
+     * Generates a new random user identifier (uid) that is guaranteed to be unique.
+     * 
+     * @param db The database for which the UID should be generated.
+     * @return A random, unique UID.
+     */
+    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.
+     * @param text the text to hash
+     * @return the result hash
+     */
+    protected String getHash( String text )
+    {
+        String hash = null;
+        try
+        {
+            hash = CryptoUtil.getSaltedPassword( text.getBytes("UTF-8") );
+        }
+        catch( NoSuchAlgorithmException e )
+        {
+            log.error( "Error creating salted SHA password hash:" + e.getMessage() );
+            hash = text;
+        }
+        catch( UnsupportedEncodingException e )
+        {
+            log.fatal("You do not have UTF-8!?!");
+        }
+        return hash;
     }
 
     /**
@@ -233,8 +335,9 @@
      * <code>String</code>
      * @param text the text to hash
      * @return the result hash
+     * @deprecated this method is retained for backwards compatibility purposes; use {@link #getHash(String)} instead
      */
-    protected String getHash( String text )
+    protected String getOldHash( String text )
     {
         String hash = null;
         try
@@ -256,4 +359,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/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/DefaultUserProfile.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/DefaultUserProfile.java?rev=682144&r1=682143&r2=682144&view=diff
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/DefaultUserProfile.java (original)
+++ incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/DefaultUserProfile.java Sun Aug  3 05:17:34 2008
@@ -1,54 +1,87 @@
-/*
- JSPWiki - a JSP-based WikiWiki clone.
+/* 
+    JSPWiki - a JSP-based WikiWiki clone.
 
- Copyright (C) 2001-2005 Janne Jalkanen (Janne.Jalkanen@iki.fi)
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU Lesser General Public License as published by
- the Free Software Foundation; either version 2.1 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- GNU Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.  
  */
 package com.ecyrd.jspwiki.auth.user;
 
+import java.io.Serializable;
 import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * Default implementation for representing wiki user information, such as the
  * login name, full name, wiki name, and e-mail address.
- * @author Janne Jalkanen
  * @author Andrew Jaquith
  * @since 2.3
  */
 
-public class DefaultUserProfile implements UserProfile
+public final class DefaultUserProfile implements UserProfile
 {
+    private static final long serialVersionUID = -5600466893735300647L;
+
     private static final String EMPTY_STRING = "";
 
     private static final String WHITESPACE = "\\s";
+    
+    private Map<String,Serializable> m_attributes = new HashMap<String,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;
 
+    /**
+     * Private constructor to prevent direct instantiation.
+     */
+    private DefaultUserProfile() {}
+
+    /**
+     * Static factory method that creates a new DefaultUserProfile
+     * and sets a unique identifier (uid) for the supplied UserDatabase.
+     * @param db the UserDatabase for which the uid should be
+     * created
+     * @return the new profile
+     */
+    protected static UserProfile newProfile( UserDatabase db )
+    {
+        UserProfile profile = new DefaultUserProfile();
+        profile.setUid( AbstractUserDatabase.generateUid( db ) );
+        return profile;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     public boolean equals( Object o )
     {
         if ( ( o != null ) && ( o instanceof UserProfile ) )
@@ -231,6 +264,7 @@
      * @param name the wiki name
      * @deprecated This method will be removed in a future release.
      */
+    @SuppressWarnings("deprecation")
     public void setWikiName( String name )
     {
     }
@@ -263,4 +297,61 @@
         }
         return arg1.equals( arg2 );
     }
+
+    //--------------------------- Attribute and lock interface implementations ---------------------------
+    
+    /**
+     * {@inheritDoc}
+     */
+    public Map<String,Serializable> getAttributes()
+    {
+        return m_attributes;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Date getLockExpiry()
+    {
+        return isLocked() ? m_lockExpiry : null;
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public long getUid()
+    {
+        return m_uid;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    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;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setLockExpiry( Date expiry )
+    {
+        m_lockExpiry = expiry;
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public void setUid( long uid )
+    {
+        m_uid = uid;
+    }
 }

Modified: incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/DuplicateUserException.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/DuplicateUserException.java?rev=682144&r1=682143&r2=682144&view=diff
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/DuplicateUserException.java (original)
+++ incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/DuplicateUserException.java Sun Aug  3 05:17:34 2008
@@ -1,21 +1,22 @@
 /* 
     JSPWiki - a JSP-based WikiWiki clone.
 
-    Copyright (C) 2001-2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU Lesser General Public License as published by
-    the Free Software Foundation; either version 2.1 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU Lesser General Public License for more details.
-
-    You should have received a copy of the GNU Lesser General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.  
  */
 package com.ecyrd.jspwiki.auth.user;
 

Modified: incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/JDBCUserDatabase.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/JDBCUserDatabase.java?rev=682144&r1=682143&r2=682144&view=diff
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/JDBCUserDatabase.java (original)
+++ incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/JDBCUserDatabase.java Sun Aug  3 05:17:34 2008
@@ -1,30 +1,30 @@
-/*
+/* 
     JSPWiki - a JSP-based WikiWiki clone.
 
-    Copyright (C) 2001-2007 JSPWiki Development Group
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU Lesser General Public License as published by
-    the Free Software Foundation; either version 2.1 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU Lesser General Public License for more details.
-
-    You should have received a copy of the GNU Lesser General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.  
  */
 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;
@@ -32,103 +32,119 @@
 import javax.sql.DataSource;
 
 import com.ecyrd.jspwiki.NoRequiredPropertyException;
-import com.ecyrd.jspwiki.TextUtil;
 import com.ecyrd.jspwiki.WikiEngine;
 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>Implementation of UserDatabase that persists {@link DefaultUserProfile}
+ * <p>
+ * Implementation of UserDatabase that persists {@link DefaultUserProfile}
  * objects to a JDBC DataSource, as might typically be provided by a web
- * container. This implementation looks up the JDBC DataSource using JNDI.
- * The JNDI name of the datasource, backing table and mapped columns used
- * by this class are configured via settings in <code>jspwiki.properties</code>.</p>
- * <p>Configurable properties are these:</p>
+ * container. This implementation looks up the JDBC DataSource using JNDI. The
+ * JNDI name of the datasource, backing table and mapped columns used by this
+ * class are configured via settings in <code>jspwiki.properties</code>.
+ * </p>
+ * <p>
+ * Configurable properties are these:
+ * </p>
  * <table>
- *   <tr>
- *   <thead>
- *     <th>Property</th>
- *     <th>Default</th>
- *     <th>Definition</th>
- *   <thead>
- *   </tr>
- *   <tr>
- *     <td><code>jspwiki.userdatabase.datasource</code></td>
- *     <td><code>jdbc/UserDatabase</code></td>
- *     <td>The JNDI name of the DataSource</td>
- *   </tr>
- *   <tr>
- *     <td><code>jspwiki.userdatabase.table</code></td>
- *     <td><code>users</code></td>
- *     <td>The table that stores the user profiles</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>
- *   </tr>
- *   <tr>
- *     <td><code>jspwiki.userdatabase.email</code></td>
- *     <td><code>email</code></td>
- *     <td>The column containing the user's e-mail address</td>
- *   </tr>
- *   <tr>
- *     <td><code>jspwiki.userdatabase.fullName</code></td>
- *     <td><code>full_name</code></td>
- *     <td>The column containing the user's full name</td>
- *   </tr>
- *   <tr>
- *     <td><code>jspwiki.userdatabase.loginName</code></td>
- *     <td><code>login_name</code></td>
- *     <td>The column containing the user's login id</td>
- *   </tr>
- *   <tr>
- *     <td><code>jspwiki.userdatabase.password</code></td>
- *     <td><code>password</code></td>
- *     <td>The column containing the user's password</td>
- *   </tr>
- *   <tr>
- *     <td><code>jspwiki.userdatabase.modified</code></td>
- *     <td><code>modified</code></td>
- *     <td>The column containing the profile's last-modified timestamp</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.roleTable</code></td>
- *     <td><code>roles</code></td>
- *     <td>The table that stores user roles. When a new user is created,
- *       a new record is inserted containing user's initial role. The
- *       table will have an ID column whose name and values correspond
- *       to the contents of the user table's login name column. It will
- *       also contain a role column (see next row).</td>
- *   </tr>
- *   <tr>
- *     <td><code>jspwiki.userdatabase.role</code></td>
- *     <td><code>role</code></td>
- *     <td>The column in the role table that stores user roles. When a new user
- *       is created, this column will be populated with the value
- *       <code>Authenticated</code>. Once created, JDBCUserDatabase does not
- *       use this column again; it is provided strictly for the convenience
- *       of container-managed authentication services.</td>
- *   </tr>
- *   <tr>
- *     <td><code>jspwiki.userdatabase.hashPrefix</code></td>
- *     <td><code>true</code></td>
- *     <td>Whether or not to prepend a prefix for the hash algorithm, <em>e.g.</em>,
- *         <code>{SHA}</code>.</td>
- *   </tr>
+ * <tr> <thead>
+ * <th>Property</th>
+ * <th>Default</th>
+ * <th>Definition</th>
+ * <thead> </tr>
+ * <tr>
+ * <td><code>jspwiki.userdatabase.datasource</code></td>
+ * <td><code>jdbc/UserDatabase</code></td>
+ * <td>The JNDI name of the DataSource</td>
+ * </tr>
+ * <tr>
+ * <td><code>jspwiki.userdatabase.table</code></td>
+ * <td><code>users</code></td>
+ * <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>
+ * </tr>
+ * <tr>
+ * <td><code>jspwiki.userdatabase.email</code></td>
+ * <td><code>email</code></td>
+ * <td>The column containing the user's e-mail address</td>
+ * </tr>
+ * <tr>
+ * <td><code>jspwiki.userdatabase.fullName</code></td>
+ * <td><code>full_name</code></td>
+ * <td>The column containing the user's full name</td>
+ * </tr>
+ * <tr>
+ * <td><code>jspwiki.userdatabase.loginName</code></td>
+ * <td><code>login_name</code></td>
+ * <td>The column containing the user's login id</td>
+ * </tr>
+ * <tr>
+ * <td><code>jspwiki.userdatabase.password</code></td>
+ * <td><code>password</code></td>
+ * <td>The column containing the user's password</td>
+ * </tr>
+ * <tr>
+ * <td><code>jspwiki.userdatabase.modified</code></td>
+ * <td><code>modified</code></td>
+ * <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
+ * record is inserted containing user's initial role. The table will have an ID
+ * column whose name and values correspond to the contents of the user table's
+ * login name column. It will also contain a role column (see next row).</td>
+ * </tr>
+ * <tr>
+ * <td><code>jspwiki.userdatabase.role</code></td>
+ * <td><code>role</code></td>
+ * <td>The column in the role table that stores user roles. When a new user is
+ * created, this column will be populated with the value
+ * <code>Authenticated</code>. Once created, JDBCUserDatabase does not use
+ * this column again; it is provided strictly for the convenience of
+ * container-managed authentication services.</td>
+ * </tr>
  * </table>
- * <p>This class hashes passwords using SHA-1. All of the underying SQL commands used by this class are implemented using
- * prepared statements, so it is immune to SQL injection attacks.</p>
- * <p>This class is typically used in conjunction with a web container's JNDI resource
- * factory. For example, Tomcat versions 4 and higher provide a basic JNDI factory
- * for registering DataSources. To give JSPWiki access to the JNDI resource named
- * by <code></code>, you would declare the datasource resource similar to this:</p>
+ * <p>
+ * This class hashes passwords using SHA-1. All of the underying SQL commands
+ * used by this class are implemented using prepared statements, so it is immune
+ * to SQL injection attacks.
+ * </p>
+ * <p>
+ * This class is typically used in conjunction with a web container's JNDI
+ * resource factory. For example, Tomcat versions 4 and higher provide a basic
+ * JNDI factory for registering DataSources. To give JSPWiki access to the JNDI
+ * resource named by <code></code>, you would declare the datasource resource
+ * similar to this:
+ * </p>
  * <blockquote><code>&lt;Context ...&gt;<br/>
  *  &nbsp;&nbsp;...<br/>
  *  &nbsp;&nbsp;&lt;Resource name="jdbc/UserDatabase" auth="Container"<br/>
@@ -137,98 +153,145 @@
  *  &nbsp;&nbsp;&nbsp;&nbsp;maxActive="8" maxIdle="4"/&gt;<br/>
  *  &nbsp;...<br/>
  * &lt;/Context&gt;</code></blockquote>
- * <p>JDBC driver JARs should be added to Tomcat's <code>common/lib</code> directory.
- * For more Tomcat 5.5 JNDI configuration examples,
- * see <a href="http://tomcat.apache.org/tomcat-5.5-doc/jndi-resources-howto.html">
- * http://tomcat.apache.org/tomcat-5.5-doc/jndi-resources-howto.html</a>.</p>
- * <p>JDBCUserDatabase commits changes as transactions if the back-end database supports them.
- * If the database supports transactions, user profile changes are saved
- * to permanent storage only when the {@link #commit()} method is called. If the database does <em>not</em>
- * support transactions, then changes are made immediately (during the {@link #save(UserProfile)}
- * method), and the {@linkplain #commit()} method no-ops. Thus, callers should always call the
- * {@linkplain #commit()} method after saving a profile to guarantee that changes are applied.</p>
+ * <p>
+ * JDBC driver JARs should be added to Tomcat's <code>common/lib</code>
+ * directory. For more Tomcat 5.5 JNDI configuration examples, see <a
+ * href="http://tomcat.apache.org/tomcat-5.5-doc/jndi-resources-howto.html">
+ * http://tomcat.apache.org/tomcat-5.5-doc/jndi-resources-howto.html</a>.
+ * </p>
+ * <p>
+ * JDBCUserDatabase commits changes as transactions if the back-end database
+ * supports them. If the database supports transactions, user profile changes
+ * are saved to permanent storage only when the {@link #commit()} method is
+ * called. If the database does <em>not</em> support transactions, then
+ * changes are made immediately (during the {@link #save(UserProfile)} method),
+ * and the {@linkplain #commit()} method no-ops. Thus, callers should always
+ * call the {@linkplain #commit()} method after saving a profile to guarantee
+ * that changes are applied.
+ * </p>
+ * 
  * @author Andrew R. Jaquith
  * @since 2.3
- */public class JDBCUserDatabase extends AbstractUserDatabase
+ */
+public class JDBCUserDatabase extends AbstractUserDatabase
 {
 
     private static final String NOTHING = "";
 
-    public static final String DEFAULT_DB_CREATED    = "created";
+    public static final String DEFAULT_DB_ATTRIBUTES = "attributes";
 
-    public static final String DEFAULT_DB_EMAIL      = "email";
+    public static final String DEFAULT_DB_CREATED = "created";
 
-    public static final String DEFAULT_DB_FULL_NAME  = "full_name";
+    public static final String DEFAULT_DB_EMAIL = "email";
 
-    public static final String DEFAULT_DB_HASH_PREFIX = "true";
+    public static final String DEFAULT_DB_FULL_NAME = "full_name";
 
-    public static final String DEFAULT_DB_JNDI_NAME  = "jdbc/UserDatabase";
+    public static final String DEFAULT_DB_JNDI_NAME = "jdbc/UserDatabase";
 
-    public static final String DEFAULT_DB_MODIFIED   = "modified";
+    public static final String DEFAULT_DB_LOCK_EXPIRY = "lock_expiry";
 
-    public static final String DEFAULT_DB_ROLE       = "role";
+    public static final String DEFAULT_DB_MODIFIED = "modified";
+
+    public static final String DEFAULT_DB_ROLE = "role";
 
     public static final String DEFAULT_DB_ROLE_TABLE = "roles";
 
-    public static final String DEFAULT_DB_TABLE      = "users";
+    public static final String DEFAULT_DB_TABLE = "users";
 
     public static final String DEFAULT_DB_LOGIN_NAME = "login_name";
 
-    public static final String DEFAULT_DB_PASSWORD   = "password";
+    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 DEFAULT_DB_WIKI_NAME  = "wiki_name";
+    public static final String PROP_DB_EMAIL = "jspwiki.userdatabase.email";
 
-    public static final String PROP_DB_CREATED       = "jspwiki.userdatabase.created";
+    public static final String PROP_DB_FULL_NAME = "jspwiki.userdatabase.fullName";
 
-    public static final String PROP_DB_EMAIL         = "jspwiki.userdatabase.email";
+    public static final String PROP_DB_DATASOURCE = "jspwiki.userdatabase.datasource";
 
-    public static final String PROP_DB_FULL_NAME     = "jspwiki.userdatabase.fullName";
+    public static final String PROP_DB_LOCK_EXPIRY = "jspwiki.userdatabase.lockExpiry";
 
-    public static final String PROP_DB_DATASOURCE    = "jspwiki.userdatabase.datasource";
+    public static final String PROP_DB_LOGIN_NAME = "jspwiki.userdatabase.loginName";
 
-    public static final String PROP_DB_HASH_PREFIX   = "jspwiki.userdatabase.hashPrefix";
+    public static final String PROP_DB_MODIFIED = "jspwiki.userdatabase.modified";
 
-    public static final String PROP_DB_LOGIN_NAME    = "jspwiki.userdatabase.loginName";
+    public static final String PROP_DB_PASSWORD = "jspwiki.userdatabase.password";
 
-    public static final String PROP_DB_MODIFIED      = "jspwiki.userdatabase.modified";
+    public static final String PROP_DB_UID = "jspwiki.userdatabase.uid";
 
-    public static final String PROP_DB_PASSWORD      = "jspwiki.userdatabase.password";
+    public static final String PROP_DB_ROLE = "jspwiki.userdatabase.role";
 
-    public static final String PROP_DB_ROLE          = "jspwiki.userdatabase.role";
+    public static final String PROP_DB_ROLE_TABLE = "jspwiki.userdatabase.roleTable";
 
-    public static final String PROP_DB_ROLE_TABLE    = "jspwiki.userdatabase.roleTable";
+    public static final String PROP_DB_TABLE = "jspwiki.userdatabase.table";
 
-    public static final String PROP_DB_TABLE         = "jspwiki.userdatabase.table";
+    public static final String PROP_DB_WIKI_NAME = "jspwiki.userdatabase.wikiName";
 
-    public static final String PROP_DB_WIKI_NAME     = "jspwiki.userdatabase.wikiName";
+    private DataSource m_ds = null;
 
-    private DataSource         m_ds                  = null;
     private String m_deleteUserByLoginName = null;
+
     private String m_deleteRoleByLoginName = null;
+
     private String m_findByEmail = null;
+
     private String m_findByFullName = null;
+
     private String m_findByLoginName = null;
+
+    private String m_findByUid = null;
+
     private String m_findByWikiName = null;
+
     private String m_renameProfile = null;
+
     private String m_renameRoles = null;
+
     private String m_updateProfile = null;
+
     private String m_findAll = null;
+
     private String m_findRoles = null;
+
     private String m_initialRole = "Authenticated";
+
     private String m_insertProfile = null;
+
     private String m_insertRole = null;
+
     private String m_userTable = null;
+
+    private String m_attributes = null;
+
     private String m_email = null;
+
     private String m_fullName = null;
-    private boolean m_hashPrefix = true;
+
+    private String m_lockExpiry = null;
+
     private String m_loginName = null;
+
     private String m_password = null;
+
     private String m_role = null;
+
     private String m_roleTable = null;
+
+    private String m_uid = null;
+    
     private String m_wikiName = null;
+
     private String m_created = null;
+
     private String m_modified = null;
-    private boolean m_sharedWithContainer = false;
+
     private boolean m_supportsCommits = false;
 
     /**
@@ -237,8 +300,9 @@
      * does not contain a user with a matching attribute, throws a
      * {@link NoSuchPrincipalException}. This method is intended to be atomic;
      * results cannot be partially committed. If the commit fails, it should
-     * roll back its state appropriately. Implementing classes that persist
-     * to the file system may wish to make this method <code>synchronized</code>.
+     * roll back its state appropriately. Implementing classes that persist to
+     * the file system may wish to make this method <code>synchronized</code>.
+     * 
      * @param loginName the login name of the user profile that shall be deleted
      */
     public void deleteByLoginName( String loginName ) throws NoSuchPrincipalException, WikiSecurityException
@@ -246,12 +310,12 @@
         // Get the existing user; if not found, throws NoSuchPrincipalException
         findByLoginName( loginName );
         Connection conn = null;
-        
+
         try
         {
             // Open the database connection
             conn = m_ds.getConnection();
-            if ( m_supportsCommits )
+            if( m_supportsCommits )
             {
                 conn.setAutoCommit( false );
             }
@@ -259,29 +323,35 @@
             PreparedStatement ps;
             // Delete user record
             ps = conn.prepareStatement( m_deleteUserByLoginName );
-            ps.setString(1, loginName );
+            ps.setString( 1, loginName );
             ps.execute();
             ps.close();
 
             // Delete role record
             ps = conn.prepareStatement( m_deleteRoleByLoginName );
-            ps.setString(1, loginName );
+            ps.setString( 1, loginName );
             ps.execute();
             ps.close();
 
             // Commit and close connection
-            if ( m_supportsCommits )
+            if( m_supportsCommits )
             {
                 conn.commit();
             }
         }
-        catch ( SQLException e )
+        catch( SQLException e )
         {
             throw new WikiSecurityException( e.getMessage() );
         }
         finally
         {
-            try { conn.close(); } catch (Exception e) {}
+            try
+            {
+                if( conn != null ) conn.close();
+            }
+            catch( Exception e )
+            {
+            }
         }
     }
 
@@ -312,21 +382,29 @@
     /**
      * @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 );
     }
 
     /**
-     * Returns all WikiNames that are stored in the UserDatabase
-     * as an array of WikiPrincipal objects. If the database does not
-     * contain any profiles, this method will return a zero-length
-     * array.
+     * Returns all WikiNames that are stored in the UserDatabase as an array of
+     * WikiPrincipal objects. If the database does not contain any profiles,
+     * this method will return a zero-length array.
+     * 
      * @return the WikiNames
      */
     public Principal[] getWikiNames() throws WikiSecurityException
     {
-        Set principals = new HashSet();
+        Set<Principal> principals = new HashSet<Principal>();
         Connection conn = null;
         try
         {
@@ -336,7 +414,7 @@
             while ( rs.next() )
             {
                 String wikiName = rs.getString( m_wikiName );
-                if ( wikiName == null )
+                if( wikiName == null )
                 {
                     log.warn( "Detected null wiki name in XMLUserDataBase. Check your user database." );
                 }
@@ -348,21 +426,27 @@
             }
             ps.close();
         }
-        catch ( SQLException e )
+        catch( SQLException e )
         {
             throw new WikiSecurityException( e.getMessage() );
         }
         finally
         {
-            try { conn.close(); } catch (Exception e) {}
+            try
+            {
+                if( conn != null ) conn.close();
+            }
+            catch( Exception e )
+            {
+            }
         }
 
-        return (Principal[])principals.toArray( new Principal[principals.size()] );
+        return principals.toArray( new Principal[principals.size()] );
     }
 
     /**
      * @see com.ecyrd.jspwiki.auth.user.UserDatabase#initialize(com.ecyrd.jspwiki.WikiEngine,
-     * java.util.Properties)
+     *      java.util.Properties)
      */
     public void initialize( WikiEngine engine, Properties props ) throws NoRequiredPropertyException
     {
@@ -370,51 +454,60 @@
         try
         {
             Context initCtx = new InitialContext();
-            Context ctx = (Context) initCtx.lookup("java:comp/env");
+            Context ctx = (Context) initCtx.lookup( "java:comp/env" );
             m_ds = (DataSource) ctx.lookup( jndiName );
 
             // Prepare the SQL selectors
             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_hashPrefix = Boolean.valueOf( props.getProperty( PROP_DB_HASH_PREFIX, DEFAULT_DB_HASH_PREFIX ) ).booleanValue();
+            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_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_findAll         = "SELECT * FROM " + m_userTable;
-            m_findByEmail     = "SELECT * FROM " + m_userTable + " WHERE " + m_email + "=?";
-            m_findByFullName  = "SELECT * FROM " + m_userTable + " WHERE " + m_fullName + "=?";
+            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_findByWikiName  = "SELECT * FROM " + m_userTable + " WHERE " + m_wikiName + "=?";
+            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 + " ("
+            // 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 (?,?,?,?,?,?,?)";
-            m_updateProfile   = "UPDATE " + m_userTable + " SET "
+                              + ") 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 + "=? WHERE " + m_loginName + "=?";
+                              + 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 );
             m_role = props.getProperty( PROP_DB_ROLE, DEFAULT_DB_ROLE );
-            m_insertRole      = "INSERT INTO " + m_roleTable + " ("
-                              + m_loginName + ","
-                              + m_role
-                              + ") VALUES (?,?)";
-            m_findRoles       = "SELECT * FROM " + m_roleTable + " WHERE " + m_loginName + "=?";
+            m_insertRole = "INSERT INTO " + m_roleTable + " (" + m_loginName + "," + m_role + ") VALUES (?,?)";
+            m_findRoles = "SELECT * FROM " + m_roleTable + " WHERE " + m_loginName + "=?";
 
             // Prepare the user delete SQL
             m_deleteUserByLoginName = "DELETE FROM " + m_userTable + " WHERE " + m_loginName + "=?";
@@ -423,14 +516,9 @@
             m_deleteRoleByLoginName = "DELETE FROM " + m_roleTable + " WHERE " + m_loginName + "=?";
 
             // Prepare the rename user/roles SQL
-            m_renameProfile   = "UPDATE " + m_userTable + " SET "
-                              + m_loginName + "=?,"
-                              + m_modified  + "=? WHERE " + m_loginName + "=?";
-            m_renameRoles     = "UPDATE " + m_roleTable + " SET "
-                              + m_loginName + "=? WHERE " + m_loginName + "=?";
-
-            // Set the "share users with container flag"
-            m_sharedWithContainer = TextUtil.isPositive( props.getProperty( PROP_SHARED_WITH_CONTAINER, "false" ) );
+            m_renameProfile = "UPDATE " + m_userTable + " SET " + m_loginName + "=?," + m_modified + "=? WHERE " + m_loginName
+                              + "=?";
+            m_renameRoles = "UPDATE " + m_roleTable + " SET " + m_loginName + "=? WHERE " + m_loginName + "=?";
         }
         catch( NamingException e )
         {
@@ -447,14 +535,20 @@
             ps.executeQuery();
             ps.close();
         }
-        catch ( SQLException e )
+        catch( SQLException e )
         {
             log.error( "JDBCUserDatabase initialization error: " + e.getMessage() );
             throw new NoRequiredPropertyException( PROP_DB_DATASOURCE, "JDBCUserDatabase initialization error: " + e.getMessage() );
         }
         finally
         {
-            try { conn.close(); } catch (Exception e) {}
+            try
+            {
+                if( conn != null ) conn.close();
+            }
+            catch( Exception e )
+            {
+            }
         }
         log.info( "JDBCUserDatabase initialized from JNDI DataSource: " + jndiName );
 
@@ -463,40 +557,37 @@
         {
             conn = m_ds.getConnection();
             DatabaseMetaData dmd = conn.getMetaData();
-            if ( dmd.supportsTransactions() )
+            if( dmd.supportsTransactions() )
             {
                 m_supportsCommits = true;
                 conn.setAutoCommit( false );
-                log.info("JDBCUserDatabase supports transactions. Good; we will use them." );
+                log.info( "JDBCUserDatabase supports transactions. Good; we will use them." );
             }
         }
-        catch ( SQLException e )
+        catch( SQLException e )
         {
-            log.warn("JDBCUserDatabase warning: user database doesn't seem to support transactions. Reason: " + e.getMessage() );
+            log.warn( "JDBCUserDatabase warning: user database doesn't seem to support transactions. Reason: " + e.getMessage() );
             throw new NoRequiredPropertyException( PROP_DB_DATASOURCE, "JDBCUserDatabase initialization error: " + e.getMessage() );
         }
         finally
         {
-            try { conn.close(); } catch (Exception e) {}
+            try
+            {
+                if( conn != null ) conn.close();
+            }
+            catch( Exception e )
+            {
+            }
         }
     }
 
     /**
-     * Determines whether the user database shares user/password data with the
-     * web container; returns <code>true</code> if the JSPWiki property
-     * <code>jspwiki.userdatabase.isSharedWithContainer</code> is <code>true</code>.
-     * @see com.ecyrd.jspwiki.auth.user.UserDatabase#isSharedWithContainer()
-     */
-    public boolean isSharedWithContainer()
-    {
-        return m_sharedWithContainer;
-    }
-
-    /**
      * @see com.ecyrd.jspwiki.auth.user.UserDatabase#rename(String, String)
      */
-    public void rename(String loginName, String newName)
-        throws NoSuchPrincipalException, DuplicateUserException, WikiSecurityException
+    public void rename( String loginName, String newName )
+                                                          throws NoSuchPrincipalException,
+                                                              DuplicateUserException,
+                                                              WikiSecurityException
     {
         // Get the existing user; if not found, throws NoSuchPrincipalException
         UserProfile profile = findByLoginName( loginName );
@@ -505,12 +596,12 @@
         try
         {
             UserProfile otherProfile = findByLoginName( newName );
-            if ( otherProfile != null )
+            if( otherProfile != null )
             {
                 throw new DuplicateUserException( "Cannot rename: the login name '" + newName + "' is already taken." );
             }
         }
-        catch ( NoSuchPrincipalException e )
+        catch( NoSuchPrincipalException e )
         {
             // Good! That means it's safe to save using the new name
         }
@@ -520,7 +611,7 @@
         {
             // Open the database connection
             conn = m_ds.getConnection();
-            if ( m_supportsCommits )
+            if( m_supportsCommits )
             {
                 conn.setAutoCommit( false );
             }
@@ -548,18 +639,24 @@
             profile.setLastModified( modDate );
 
             // Commit and close connection
-            if ( m_supportsCommits )
+            if( m_supportsCommits )
             {
                 conn.commit();
             }
         }
-        catch ( SQLException e )
+        catch( SQLException e )
         {
             throw new WikiSecurityException( e.getMessage() );
         }
         finally
         {
-            try { conn.close(); } catch (Exception e) {}
+            try
+            {
+                if( conn != null ) conn.close();
+            }
+            catch( Exception e )
+            {
+            }
         }
     }
 
@@ -576,28 +673,29 @@
         {
             existingProfile = findByLoginName( loginName );
         }
-        catch ( NoSuchPrincipalException e )
+        catch( NoSuchPrincipalException e )
         {
             // Existing profile will be null
         }
 
         // Get a clean password from the passed profile.
-        // Blank password is the same as null, which means we re-use the existing one.
+        // Blank password is the same as null, which means we re-use the
+        // existing one.
         String password = profile.getPassword();
-        String existingPassword = ( existingProfile == null ) ? null : existingProfile.getPassword();
-        if ( NOTHING.equals( password ) )
+        String existingPassword = (existingProfile == null) ? null : existingProfile.getPassword();
+        if( NOTHING.equals( password ) )
         {
             password = null;
         }
-        if ( password == null)
+        if( password == null )
         {
             password = existingPassword;
         }
 
         // If password changed, hash it before we save
-        if ( !password.equals( existingPassword ) )
+        if( !password.equals( existingPassword ) )
         {
-            password =  m_hashPrefix ? SHA_PREFIX + getHash( password ) : getHash( password );
+            password = getHash( password );
         }
 
         Connection conn = null;
@@ -605,47 +703,54 @@
         {
             // Open the database connection
             conn = m_ds.getConnection();
-            if ( m_supportsCommits )
+            if( m_supportsCommits )
             {
                 conn.setAutoCommit( false );
             }
 
             Timestamp ts = new Timestamp( System.currentTimeMillis() );
             Date modDate = new Date( ts.getTime() );
-            if ( existingProfile == null )
+            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();
 
-                // Insert role record if no roles yet
-                if ( m_sharedWithContainer )
+                // Insert new role record
+                ps = conn.prepareStatement( m_findRoles );
+                ps.setString( 1, profile.getLoginName() );
+                ResultSet rs = ps.executeQuery();
+                int roles = 0;
+                while ( rs.next() )
                 {
-                    ps = conn.prepareStatement( m_findRoles );
+                    roles++;
+                }
+                ps.close();
+                if( roles == 0 )
+                {
+                    ps = conn.prepareStatement( m_insertRole );
                     ps.setString( 1, profile.getLoginName() );
-                    ResultSet rs = ps.executeQuery();
-                    int roles = 0;
-                    while ( rs.next() )
-                    {
-                        roles++;
-                    }
+                    ps.setString( 2, m_initialRole );
+                    ps.execute();
                     ps.close();
-                    if ( roles == 0 )
-                    {
-                        ps = conn.prepareStatement( m_insertRole );
-                        ps.setString( 1, profile.getLoginName() );
-                        ps.setString( 2, m_initialRole );
-                        ps.execute();
-                        ps.close();
-                    }
                 }
 
                 // Set the profile creation time
@@ -655,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();
             }
@@ -668,28 +784,37 @@
             profile.setLastModified( modDate );
 
             // Commit and close connection
-            if ( m_supportsCommits )
+            if( m_supportsCommits )
             {
                 conn.commit();
             }
         }
-        catch ( SQLException e )
+        catch( SQLException e )
         {
             throw new WikiSecurityException( e.getMessage() );
         }
         finally
         {
-            try { conn.close(); } catch (Exception e) {}
+            try
+            {
+                if( conn != null ) conn.close();
+            }
+            catch( Exception e )
+            {
+            }
         }
     }
 
     /**
-     *
-     * @param rs
-     * @return
+     * Private method that returns the first {@link 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;
@@ -699,48 +824,93 @@
         {
             // Open the database connection
             conn = m_ds.getConnection();
-            if ( m_supportsCommits )
+            if( m_supportsCommits )
             {
                 conn.setAutoCommit( false );
             }
 
             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() )
             {
-                if ( profile != null )
+                if( profile != null )
                 {
                     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.setFullname( rs.getString( m_fullName ) );
                 profile.setLastModified( rs.getTimestamp( m_modified ) );
-                profile.setLoginName( rs.getString( m_loginName ) ) ;
+                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<String,? 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();
         }
-        catch ( SQLException e )
+        catch( SQLException e )
         {
             throw new NoSuchPrincipalException( e.getMessage() );
         }
         finally
         {
-            try { conn.close(); } catch (Exception e) {}
+            try
+            {
+                if( conn != null ) conn.close();
+            }
+            catch( Exception e )
+            {
+            }
         }
 
-        if ( !found )
+        if( !found )
         {
-            throw new NoSuchPrincipalException("Could not find profile in database!");
+            throw new NoSuchPrincipalException( "Could not find profile in database!" );
         }
-        if ( !unique )
+        if( !unique )
         {
-            throw new NoSuchPrincipalException("More than one profile in database!");
+            throw new NoSuchPrincipalException( "More than one profile in database!" );
         }
         return profile;
 

Modified: incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/UserDatabase.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/UserDatabase.java?rev=682144&r1=682143&r2=682144&view=diff
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/UserDatabase.java (original)
+++ incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/UserDatabase.java Sun Aug  3 05:17:34 2008
@@ -1,21 +1,22 @@
-/*
- JSPWiki - a JSP-based WikiWiki clone.
+/* 
+    JSPWiki - a JSP-based WikiWiki clone.
 
- Copyright (C) 2001-2005 Janne Jalkanen (Janne.Jalkanen@iki.fi)
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU Lesser General Public License as published by
- the Free Software Foundation; either version 2.1 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- GNU Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.  
  */
 package com.ecyrd.jspwiki.auth.user;
 
@@ -126,6 +127,17 @@
 
     /**
      * Looks up and returns the first {@link 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
+     * {@link 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 {@link 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
      * {@link NoSuchPrincipalException}.
@@ -150,13 +162,6 @@
     public void initialize( WikiEngine engine, Properties props ) throws NoRequiredPropertyException;
 
     /**
-     * Returns <code>true</code> if this user database shares user/password data with the
-     * web container; <code>false</false> otherwise.
-     * @return the result
-     */
-    public boolean isSharedWithContainer();
-
-    /**
      * Factory method that instantiates a new user profile.
      * The {@link UserProfile#isNew()} method of profiles created using
      * this method should return <code>true</code>.

Modified: incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/UserProfile.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/UserProfile.java?rev=682144&r1=682143&r2=682144&view=diff
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/UserProfile.java (original)
+++ incubator/jspwiki/branches/JSPWIKI_2_9_STRIPES_BRANCH/src/com/ecyrd/jspwiki/auth/user/UserProfile.java Sun Aug  3 05:17:34 2008
@@ -1,37 +1,51 @@
 /* 
     JSPWiki - a JSP-based WikiWiki clone.
 
-    Copyright (C) 2001-2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU Lesser General Public License as published by
-    the Free Software Foundation; either version 2.1 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU Lesser General Public License for more details.
-
-    You should have received a copy of the GNU Lesser General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.  
  */
 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/Serializable attributes, and store
+ * a unique ID. Locks are checked by {@link com.ecyrd.jspwiki.auth.AuthenticationManager};
+ * if a profile is locked, the user cannot log with that profile.
  * @author Andrew Jaquith
  * @since 2.3
  */
-public interface UserProfile
+public interface UserProfile extends Serializable
 {
 
     /**
+     * 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<String,Serializable> getAttributes();
+
+    /**
      * Returns the creation date.
      * @return the creation date
      */
@@ -56,6 +70,18 @@
     public Date getLastModified();
 
     /**
+     * Returns the date/time of expiration of the profile's lock, if it has been
+     * previously locked via {@link #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 {@link #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
      */
@@ -73,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.
@@ -80,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 {@link #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.
@@ -112,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
      * {@link com.ecyrd.jspwiki.auth.AuthenticationManager#login(WikiSession, String, String)},
@@ -135,6 +185,13 @@
     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.
+     * @param uid the unique identifier to set
+     */
+    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.