Commit 8eb6fd60 authored by Matt Tucker's avatar Matt Tucker Committed by matt

Refactored user classes.


git-svn-id: http://svn.igniterealtime.org/svn/repos/messenger/trunk@691 b35dd754-fafc-0310-a699-88a17e54d16e
parent d0ac74dc
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/i18n" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src/i18n" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/java" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/plugins/broadcast/src/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" /> <excludeFolder url="file://$MODULE_DIR$/target" />
</content> </content>
......
...@@ -32,17 +32,14 @@ ...@@ -32,17 +32,14 @@
<adminDN></adminDN> <adminDN></adminDN>
<adminPassword></adminPassword> <adminPassword></adminPassword>
</ldap> </ldap>
<UserProvider> <provider>
<properties> <user>
<className>org.jivesoftware.messenger.ldap.LdapUserPropertiesProvider</className> <className>org.jivesoftware.messenger.ldap.LdapUserProvider</className>
</properties> </user>
<info> <auth>
<className>org.jivesoftware.messenger.ldap.LdapUserInfoProvider</className> <className>org.jivesoftware.messenger.ldap.LdapAuthProvider</className>
</info> </auth>
</UserProvider> </provider>
<AuthProvider>
<className>org.jivesoftware.messenger.ldap.LdapAuthProvider</className>
</AuthProvider>
--> -->
<!-- End Example LDAP Settings --> <!-- End Example LDAP Settings -->
</jive> </jive>
\ No newline at end of file
...@@ -160,26 +160,6 @@ public class DbConnectionManager { ...@@ -160,26 +160,6 @@ public class DbConnectionManager {
} }
/**
* Close the prepared statement and connection
* @param pstmt the <code>PreparedStatement</code> to close.
* @param con the <code>Connection</code> to close.
*/
public static void close(PreparedStatement pstmt, Connection con) {
try {
if (pstmt != null) pstmt.close();
}
catch (Exception e) {
Log.error(e);
}
try {
if (con != null) con.close();
}
catch (Exception e) {
Log.error(e);
}
}
/** /**
* Creates a scroll insensitive Statement if the JDBC driver supports it, or a normal * Creates a scroll insensitive Statement if the JDBC driver supports it, or a normal
* Statement otherwise. * Statement otherwise.
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
* a copy of which is included in this distribution. * a copy of which is included in this distribution.
*/ */
package org.jivesoftware.messenger.auth; package org.jivesoftware.messenger;
import org.jivesoftware.util.CacheSizes; import org.jivesoftware.util.CacheSizes;
import org.jivesoftware.util.StringUtils; import org.jivesoftware.util.StringUtils;
......
...@@ -9,103 +9,157 @@ ...@@ -9,103 +9,157 @@
* a copy of which is included in this distribution. * a copy of which is included in this distribution.
*/ */
package org.jivesoftware.messenger.user.spi; package org.jivesoftware.messenger;
import org.jivesoftware.database.DbConnectionManager; import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.util.Log; import org.jivesoftware.util.Log;
import org.jivesoftware.messenger.user.User; import org.jivesoftware.util.Cache;
import org.jivesoftware.messenger.user.UserPropertiesProvider; import org.jivesoftware.util.CacheManager;
import java.util.*;
import java.sql.*;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Hashtable;
import java.util.Map;
/** /**
* <p>Database implementation of the UserPropertiesProvider interface.</p> * Manages VCard information for users.
* *
* @author Matt Tucker * @author Matt Tucker
* @author Bruce Ritchie
* @author Iain Shigeoka
*
* @see User
*/ */
public class DbUserPropertiesProvider implements UserPropertiesProvider { public class VCardManager {
private static final String LOAD_PROPERTIES = private static final String LOAD_PROPERTIES =
"SELECT name, propValue FROM jiveUserProp WHERE username=?";
private static final String DELETE_PROPERTY =
"DELETE FROM jiveUserProp WHERE username=? AND name=?";
private static final String UPDATE_PROPERTY =
"UPDATE jiveUserProp SET propValue=? WHERE name=? AND username=?";
private static final String INSERT_PROPERTY =
"INSERT INTO jiveUserProp (username, name, propValue) VALUES (?, ?, ?)";
private static final String LOAD_VPROPERTIES =
"SELECT name, propValue FROM jiveVCard WHERE username=?"; "SELECT name, propValue FROM jiveVCard WHERE username=?";
private static final String DELETE_VPROPERTY = private static final String DELETE_PROPERTY =
"DELETE FROM jiveVCard WHERE username=? AND name=?"; "DELETE FROM jiveVCard WHERE username=? AND name=?";
private static final String UPDATE_VPROPERTY = private static final String UPDATE_PROPERTY =
"UPDATE jiveVCard SET propValue=? WHERE name=? AND username=?"; "UPDATE jiveVCard SET propValue=? WHERE name=? AND username=?";
private static final String INSERT_VPROPERTY = private static final String INSERT_PROPERTY =
"INSERT INTO jiveVCard (username, name, propValue) VALUES (?, ?, ?)"; "INSERT INTO jiveVCard (username, name, propValue) VALUES (?, ?, ?)";
private static VCardManager instance = new VCardManager();
public static VCardManager getInstance() {
return instance;
}
private Cache vcardCache;
private VCardManager() {
CacheManager.initializeCache("vcardCache", 512 * 1024);
vcardCache = CacheManager.getCache("vcardCache");
}
/** /**
* Create a new DbUserPropertiesProvider. * Obtains the user's vCard information for a given vcard property name.
* Advanced user systems can use vCard
* information to link to user directory information or store other
* relevant user information.
*
* @param name The name of the vcard property to retrieve
* @return The vCard value found
*/ */
public DbUserPropertiesProvider() { public String getVCardProperty(String username, String name) {
Map<String,String> properties = (Map<String,String>)vcardCache.get(username);
if (properties == null) {
properties = loadPropertiesFromDb(username);
vcardCache.put(username, properties);
}
return properties.get(name);
} }
/** /**
* Loads properties from the database. * Sets the user's vCard information. Advanced user systems can use vCard
* information to link to user directory information or store other
* relevant user information.
*
* @param name The name of the vcard property
* @param value The value of the vcard property
*/ */
private synchronized Map loadPropertiesFromDb(String username, boolean isVcard) { public void setVCardProperty(String username, String name, String value) {
Map props = new Hashtable(); Map<String,String> properties = (Map<String,String>)vcardCache.get(username);
Connection con = null; if (properties == null) {
PreparedStatement pstmt = null; properties = loadPropertiesFromDb(username);
vcardCache.put(username, properties);
try { }
con = DbConnectionManager.getConnection(); // See if we need to update a property value or insert a new one.
if (isVcard) { if (properties.containsKey(name)) {
pstmt = con.prepareStatement(LOAD_VPROPERTIES); // Only update the value in the database if the property value
} // has changed.
else { if (!(value.equals(properties.get(name)))) {
pstmt = con.prepareStatement(LOAD_PROPERTIES); properties.put(name, value);
} updatePropertyInDb(username, name, value);
pstmt.setString(1, username);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
props.put(rs.getString(1), rs.getString(2));
} }
} }
catch (SQLException e) { else {
Log.error(e); properties.put(name, value);
insertPropertyIntoDb(username, name, value);
} }
finally { }
try { if (pstmt != null) { pstmt.close(); } }
catch (Exception e) { Log.error(e); } /**
try { if (con != null) { con.close(); } } * Deletes a given vCard property from the user account.
catch (Exception e) { Log.error(e); } *
* @param name The name of the vcard property to remove
*/
public void deleteVCardProperty(String username, String name) {
Map<String,String> properties = (Map<String,String>)vcardCache.get(username);
if (properties == null) {
properties = loadPropertiesFromDb(username);
vcardCache.put(username, properties);
} }
return props; properties.remove(name);
deletePropertyFromDb(username, name);
} }
/** /**
* Inserts a new property into the datatabase. * Obtain an iterator for all vcard property names.
*
* @return the iterator over all vcard property names.
*/ */
private void insertPropertyIntoDb(String username, String name, String value, boolean isVcard) { public Collection<String> getVCardPropertyNames(String username) {
Map<String,String> properties = (Map<String,String>)vcardCache.get(username);
if (properties == null) {
properties = loadPropertiesFromDb(username);
vcardCache.put(username, properties);
}
return Collections.unmodifiableCollection(properties.keySet());
}
private Map<String,String> loadPropertiesFromDb(String username) {
synchronized (username.intern()) {
Map<String,String> properties = new Hashtable<String,String>();
java.sql.Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(LOAD_PROPERTIES);
pstmt.setString(1, username);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
properties.put(rs.getString(1), rs.getString(2));
}
}
catch (SQLException e) {
Log.error(e);
}
finally {
try { if (pstmt != null) { pstmt.close(); } }
catch (Exception e) { Log.error(e); }
try { if (con != null) { con.close(); } }
catch (Exception e) { Log.error(e); }
}
return properties;
}
}
private void insertPropertyIntoDb(String username, String name, String value) {
Connection con = null; Connection con = null;
PreparedStatement pstmt = null; PreparedStatement pstmt = null;
try { try {
con = DbConnectionManager.getConnection(); con = DbConnectionManager.getConnection();
if (isVcard) { pstmt = con.prepareStatement(INSERT_PROPERTY);
pstmt = con.prepareStatement(INSERT_VPROPERTY);
}
else {
pstmt = con.prepareStatement(INSERT_PROPERTY);
}
pstmt.setString(1, username); pstmt.setString(1, username);
pstmt.setString(2, name); pstmt.setString(2, name);
pstmt.setString(3, value); pstmt.setString(3, value);
...@@ -122,20 +176,12 @@ public class DbUserPropertiesProvider implements UserPropertiesProvider { ...@@ -122,20 +176,12 @@ public class DbUserPropertiesProvider implements UserPropertiesProvider {
} }
} }
/** private void updatePropertyInDb(String username, String name, String value) {
* Updates a property value in the database.
*/
private void updatePropertyInDb(String username, String name, String value, boolean isVcard) {
Connection con = null; Connection con = null;
PreparedStatement pstmt = null; PreparedStatement pstmt = null;
try { try {
con = DbConnectionManager.getConnection(); con = DbConnectionManager.getConnection();
if (isVcard) { pstmt = con.prepareStatement(UPDATE_PROPERTY);
pstmt = con.prepareStatement(UPDATE_VPROPERTY);
}
else {
pstmt = con.prepareStatement(UPDATE_PROPERTY);
}
pstmt.setString(1, value); pstmt.setString(1, value);
pstmt.setString(2, name); pstmt.setString(2, name);
pstmt.setString(3, username); pstmt.setString(3, username);
...@@ -152,20 +198,12 @@ public class DbUserPropertiesProvider implements UserPropertiesProvider { ...@@ -152,20 +198,12 @@ public class DbUserPropertiesProvider implements UserPropertiesProvider {
} }
} }
/** private void deletePropertyFromDb(String username, String name) {
* Deletes a property from the db.
*/
private void deletePropertyFromDb(String username, String name, boolean isVcard) {
Connection con = null; Connection con = null;
PreparedStatement pstmt = null; PreparedStatement pstmt = null;
try { try {
con = DbConnectionManager.getConnection(); con = DbConnectionManager.getConnection();
if (isVcard) { pstmt = con.prepareStatement(DELETE_PROPERTY);
pstmt = con.prepareStatement(DELETE_VPROPERTY);
}
else {
pstmt = con.prepareStatement(DELETE_PROPERTY);
}
pstmt.setString(1, username); pstmt.setString(1, username);
pstmt.setString(2, name); pstmt.setString(2, name);
pstmt.executeUpdate(); pstmt.executeUpdate();
...@@ -180,36 +218,4 @@ public class DbUserPropertiesProvider implements UserPropertiesProvider { ...@@ -180,36 +218,4 @@ public class DbUserPropertiesProvider implements UserPropertiesProvider {
catch (Exception e) { Log.error(e); } catch (Exception e) { Log.error(e); }
} }
} }
}
public void deleteVcardProperty(String username, String name) { \ No newline at end of file
deletePropertyFromDb(username, name, true);
}
public void deleteUserProperty(String username, String name) {
deletePropertyFromDb(username, name, false);
}
public void insertVcardProperty(String username, String name, String value) {
insertPropertyIntoDb(username, name, value, true);
}
public void insertUserProperty(String username, String name, String value) {
insertPropertyIntoDb(username, name, value, false);
}
public void updateVcardProperty(String username, String name, String value) {
updatePropertyInDb(username, name, value, true);
}
public void updateUserProperty(String username, String name, String value) {
updatePropertyInDb(username, name, value, false);
}
public Map getVcardProperties(String username) {
return loadPropertiesFromDb(username, true);
}
public Map getUserProperties(String username) {
return loadPropertiesFromDb(username, false);
}
}
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
package org.jivesoftware.messenger; package org.jivesoftware.messenger;
import org.xmpp.packet.JID; import org.xmpp.packet.JID;
import org.jivesoftware.messenger.user.RosterManager; import org.jivesoftware.messenger.roster.RosterManager;
import org.jivesoftware.messenger.user.UserManager; import org.jivesoftware.messenger.user.UserManager;
import org.jivesoftware.messenger.handler.IQRegisterHandler; import org.jivesoftware.messenger.handler.IQRegisterHandler;
import org.jivesoftware.messenger.handler.PresenceUpdateHandler; import org.jivesoftware.messenger.handler.PresenceUpdateHandler;
...@@ -24,6 +24,7 @@ import org.jivesoftware.messenger.disco.ServerFeaturesProvider; ...@@ -24,6 +24,7 @@ import org.jivesoftware.messenger.disco.ServerFeaturesProvider;
import org.jivesoftware.messenger.disco.ServerItemsProvider; import org.jivesoftware.messenger.disco.ServerItemsProvider;
import org.jivesoftware.messenger.disco.IQDiscoInfoHandler; import org.jivesoftware.messenger.disco.IQDiscoInfoHandler;
import org.jivesoftware.messenger.muc.MultiUserChatServer; import org.jivesoftware.messenger.muc.MultiUserChatServer;
import org.jivesoftware.messenger.roster.RosterManager;
import java.util.List; import java.util.List;
......
...@@ -14,17 +14,15 @@ package org.jivesoftware.messenger.auth; ...@@ -14,17 +14,15 @@ package org.jivesoftware.messenger.auth;
import org.jivesoftware.util.LocaleUtils; import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log; import org.jivesoftware.util.Log;
import org.jivesoftware.util.StringUtils; import org.jivesoftware.util.StringUtils;
import org.jivesoftware.messenger.auth.spi.AuthTokenImpl; import org.jivesoftware.util.ClassUtils;
import org.jivesoftware.messenger.JiveGlobals;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
/** /**
* An abstract class that defines a framework for providing authentication services in Jive. The * Authentication service.
* static getAuthToken(String, String), *
* getAuthToken(HttpServletRequest, HttpServletResponse), and getAnonymousAuthToken()
* methods should be called directly from applications using Jive in order to obtain an
* AuthToken.<p>
* <p/>
* Users of Jive that wish to change the AuthProvider implementation used to authenticate users * Users of Jive that wish to change the AuthProvider implementation used to authenticate users
* can set the <code>AuthProvider.className</code> Jive property. For example, if * can set the <code>AuthProvider.className</code> Jive property. For example, if
* you have altered Jive to use LDAP for user information, you'd want to send a custom * you have altered Jive to use LDAP for user information, you'd want to send a custom
...@@ -38,28 +36,27 @@ import java.security.NoSuchAlgorithmException; ...@@ -38,28 +36,27 @@ import java.security.NoSuchAlgorithmException;
* implement auto-login.<p> * implement auto-login.<p>
* *
* @author Matt Tucker * @author Matt Tucker
* @author Iain Shigeoka
*/ */
public abstract class AuthFactory { public class AuthFactory {
/**
* Name of the key in a user's session that AuthToken tokens are customarily stored at.
*/
public static final String SESSION_AUTHORIZATION = "jive.authToken";
private static AuthProvider authProvider = null; private static AuthProvider authProvider = null;
private static MessageDigest digest;
private static MessageDigest sha;
/**
* Initializes encryption and decryption ciphers using the secret key found in the Jive property
* "cookieKey". If a secret key has not been created yet, it is automatically generated and
* saved.
*/
static { static {
authProvider = AuthProviderFactory.getAuthProvider(); // Load an auth provider.
String className = JiveGlobals.getXMLProperty("provider.auth.className",
"org.jivesoftware.messenger.auth.DefaultAuthProvider");
try {
Class c = ClassUtils.forName(className);
authProvider = (AuthProvider)c.newInstance();
}
catch (Exception e) {
Log.error("Error loading auth provider: " + className, e);
authProvider = new DefaultAuthProvider();
}
// Create a message digest instance.
try { try {
sha = MessageDigest.getInstance("SHA"); digest = MessageDigest.getInstance("SHA");
} }
catch (NoSuchAlgorithmException e) { catch (NoSuchAlgorithmException e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e); Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
...@@ -67,103 +64,75 @@ public abstract class AuthFactory { ...@@ -67,103 +64,75 @@ public abstract class AuthFactory {
} }
/** /**
* <p>Determines if the authentication system supports the use of plain-text passwords.</p> * Returns true if the currently installed {@link AuthProvider} supports authentication
* <p>Some servers may wish to disable plain text password support because the passwords are * using plain-text passwords according to JEP-0078. Plain-text authentication is
* easy to steal in transit unless SSL is used. Plain-text passwords will be verified via the * not secure and should generally only be used over a TLS/SSL connection.
* getAuthToken(String username, String password) method.</p>
* <p>Notice that this flag is used to indicate to clients whether the server will accept
* plain text passwords. It does not have to imply that passwords are stored as plain text.
* For example, passwords may be stored as MD5 hashes of the password in the database. Plain
* text passwords can be supported by simply taking the plain-text password sent from the client,
* MD5 hashing it, and seeing if it matches the hash stored in the database.</p>
* *
* @return True if plain text passwords are supported on the server * @return true if plain text password authentication is supported.
*/ */
public static boolean isPlainSupported() { public static boolean isPlainSupported() {
return authProvider.isPlainSupported(); return authProvider.isPlainSupported();
} }
/** /**
* <p>Determines if the authentication system supports the use of digest authentication.</p> * Returns true if the currently installed {@link AuthProvider} supports
* <p>Some servers may wish to only enable plain text password support because the passwords are * digest authentication according to JEP-0078.
* easy to steal using plain-text in transit unless SSL is used.</p>
* <p/>
* <p>Perhaps ironically, digest authentication requires plain-text passwords to be stored
* for each user. The digest protocol protects the password sent over the network by SHA-1
* digesting it with a unique token. To check the digest, the plain-text copy of the password
* must be available on the server (thus increasing the need for keeping the backend store
* secure). If your user system cannot store passwords in plain text, then you should return
* false so digest authentication is not used. If you must use plain-text on an insecure network,
* it is recommend that users connect with SSL to protect the passwords in transit.</p>
* *
* @return True if digest authentication is supported on the server * @return true if digest authentication is supported.
*/ */
public static boolean isDigestSupported() { public static boolean isDigestSupported() {
return authProvider.isDigestSupported(); return authProvider.isDigestSupported();
} }
/** /**
* Returns the AuthToken token associated with the specified username and password. If the * Authenticates a user with a username and plain text password and returns and
* username and password do not match the record of any user in the system, the method throws an * AuthToken. If the username and password do not match the record of
* UnauthorizedException.<p> * any user in the system, this method throws an UnauthorizedException.
* *
* @param username the username to create an AuthToken with. * @param username the username.
* @param password the password to create an AuthToken with. * @param password the password.
* @return an AuthToken token if the username and password are correct. * @return an AuthToken token if the username and password are correct.
* @throws UnauthorizedException if the username and password do not match any existing user. * @throws UnauthorizedException if the username and password do not match any existing user.
*/ */
public static AuthToken getAuthToken(String username, String password) public static AuthToken authenticate(String username, String password)
throws UnauthorizedException throws UnauthorizedException
{ {
authProvider.authenticate(username, password); authProvider.authenticate(username, password);
return new AuthTokenImpl(username); return new AuthToken(username);
} }
/** /**
* <p>Returns the AuthToken token associated with the specified username, unique session token, and * Authenticates a user with a username, token, and digest and returns an AuthToken.
* digest generated from the password and token according to the Jabber digest auth protocol.</p> * The digest should be generated using the {@link #createDigest(String, String)} method.
* <p>If the username and digest do not match the record of any user in the system, the method throws an * If the username and digest do not match the record of any user in the system, the
* UnauthorizedException.<p> * method throws an UnauthorizedException.
* *
* @param username the username to create an AuthToken with. * @param username the username.
* @param token the token that was used with plain-text password to generate the digest * @param token the token that was used with plain-text password to generate the digest.
* @param digest The digest generated from plain-text password and unique token * @param digest the digest generated from plain-text password and unique token.
* @return an AuthToken token if the username and digest are correct for the user's password and given token. * @return an AuthToken token if the username and digest are correct for the user's
* @throws UnauthorizedException if the username and password do not match any existing user. * password and given token.
* @throws UnauthorizedException if the username and password do not match any
* existing user.
*/ */
public static AuthToken getAuthToken(String username, String token, String digest) public static AuthToken authenticate(String username, String token, String digest)
throws UnauthorizedException { throws UnauthorizedException
{
authProvider.authenticate(username, token, digest); authProvider.authenticate(username, token, digest);
return new AuthTokenImpl(username); return new AuthToken(username);
}
/**
* The same token can be used for all anonymous users, so cache it.
*/
private static final AuthToken anonymousAuth = new AuthTokenImpl(null);
/**
* Returns an anonymous user AuthToken.
*
* @return an anonymous AuthToken token.
*/
public static AuthToken getAnonymousAuthToken() {
return anonymousAuth;
} }
/** /**
* Utility method that digests the given password and token according to the Jabber * Returns a digest given a token and password, according to JEP-0078.
* digest auth protocol. The result of createPasswordTokenDigest() can be
* String.equalsIgnoreCase() against the authentication digest passed into
* implementations of {@link #getAuthToken(String, String, String) }.
* The algorithm simply SHA-1 digests the token and password.
* *
* @param token the token used in the digest. * @param token the token used in the digest.
* @param password the plain-text password to be digested. * @param password the plain-text password to be digested.
* @return the digested result as a hex string following Jabber digest auth protocol guidelines. * @return the digested result as a hex string.
*/ */
public static String createTokenPasswordDigest(String token, String password) { public static String createDigest(String token, String password) {
sha.update(token.getBytes()); synchronized (digest) {
return StringUtils.encodeHex(sha.digest(password.getBytes())); digest.update(token.getBytes());
return StringUtils.encodeHex(digest.digest(password.getBytes()));
}
} }
} }
\ No newline at end of file
...@@ -11,118 +11,70 @@ ...@@ -11,118 +11,70 @@
package org.jivesoftware.messenger.auth; package org.jivesoftware.messenger.auth;
import org.jivesoftware.messenger.user.UserNotFoundException;
/** /**
* The essential interface to implement when creating an * Provider interface for authentication. Users that wish to integrate with
* authentication service plug-in.<p> * their own authentication system must implement this class and then register
* the implementation with Jive Messenger in the <tt>jive-messenger.xml</tt>
* file. An entry in that file would look like the following:
* *
* Implementations of auth provider strictly handles the persistent * <pre>
* storage access for authentication in Messenger. The auth provider * &lt;provider&gt;
* is the only Messenger system dealing with users that does not necessarily * &lt;auth&gt;
* have to associate a username with a user ID. This keeps the auth * &lt;className&gt;com.foo.auth.CustomAuthProvider&lt;/className&gt;
* provider completely separate from the user system. * &lt;/auth&gt;
* &lt;/provider&gt;</pre>
* *
* @author Iain Shigeoka * @author Matt Tucker
*/ */
public interface AuthProvider { public interface AuthProvider {
/** /**
* <p>Determines if the authentication system supports the use of * Returns true if this AuthProvider supports authentication using plain-text
* plain-text passwords.</p> * passwords according to JEP--0078. Plain text authentication is not secure
* <p/> * and should generally only be used for a TLS/SSL connection.
* <p>Some servers may wish to disable plain text password
* support because the passwords are easy to steal in
* transit unless SSL is used. Plain-text passwords will be verified via the
* getAuthToken(String username, String password) method.</p>
* <p/>
* <p>Notice that this flag is used to indicate to clients
* whether the server will accept plain text passwords. It
* does not have to imply that passwords are stored as plain text.
* For example, passwords may be stored as MD5 hashes of the
* password in the database. Plain text passwords can be supported
* by simply taking the plain-text password sent from the client,
* MD5 hashing it, and seeing if it matches the hash stored in the
* database.</p>
* *
* @return True if plain text passwords are supported on the server * @return true if plain text password authentication is supported by
* this AuthProvider.
*/ */
boolean isPlainSupported(); boolean isPlainSupported();
/** /**
* <p>Determines if the authentication system supports the use * Returns true if this AuthProvider supports digest authentication
* of digest authentication.</p> * according to JEP-0078.
* <p/>
* <p>Some servers may wish to only enable plain text password
* support because the passwords are easy to steal using
* plain-text in transit unless SSL is used.</p>
* <p/>
* <p>Perhaps ironically, digest authentication requires plain-text
* passwords to be stored for each user. The digest protocol
* protects the password sent over the network by SHA-1 digesting
* it with a unique token. To check the digest, the plain-text copy
* of the password must be available on the server (thus increasing
* the need for keeping the backend store secure). If your user
* system cannot store passwords in plain text, then you should return
* false so digest authentication is not used. If you must use plain-text
* on an insecure network, it is recommend that users connect with
* SSL to protect the passwords in transit.</p>
* *
* @return true if digest authentication is supported on the server. * @return true if digest authentication is supported by this
* AuthProvider.
*/ */
boolean isDigestSupported(); boolean isDigestSupported();
/** /**
* <p>Returns if the username and password are valid otherwise the method throws an * Returns if the username and password are valid; otherwise this
* UnauthorizedException.<p> * method throws an UnauthorizedException.<p>
* *
* @param username the username to create an AuthToken with. * If {@link #isPlainSupported()} returns false, this method should
* @param password the password to create an AuthToken with. * throw an UnsupportedOperationException.
* @throws UnauthorizedException if the username and password *
* do not match any existing user. * @param username the username.
* @param password the passwordl
* @throws UnauthorizedException if the username and password do
* not match any existing user.
*/ */
void authenticate(String username, String password) throws UnauthorizedException; void authenticate(String username, String password) throws UnauthorizedException;
/** /**
* <p>Returns the AuthToken token associated with the specified * Returns if the username, token, and digest are valid; otherwise this
* username, unique session token, and digest generated from the * method throws an UnauthorizedException.<p>
* password and token according to the Jabber digest auth protocol.</p> *
* <p/> * If {@link #isDigestSupported()} returns false, this method should
* <p>If the username and digest do not match the record of * throw an UnsupportedOperationException.
* any user in the system, the method throws an UnauthorizedException.<p>
* *
* @param username the username to create an AuthToken with. * @param username the username.
* @param token the token that was used with plain-text * @param token the token that was used with plain-text password to
* password to generate the digest * generate the digest.
* @param digest The digest generated from plain-text password and unique token * @param digest the digest generated from plain-text password and unique token.
* @throws UnauthorizedException if the username and password * @throws UnauthorizedException if the username and password
* do not match any existing user. * do not match any existing user.
*/ */
void authenticate(String username, String token, String digest) void authenticate(String username, String token, String digest)
throws UnauthorizedException; throws UnauthorizedException;
}
/** \ No newline at end of file
* <p>Update the password for the given user.</p>
* <p/>
* <p>Sets the users's password. The password should be passed
* in as plain text. The way the password is stored is
* implementation dependent. However, it is recommended
* to at least hash passwords with an algorithm such as
* MD5 if you don't need to support digest authentication.</p>
* <p/>
* <p>If you don't want people to change their password through
* Messenger just throw UnauthorizedException.</p>
*
* @param username The username of the user who's password is changing
* @param password The new password for the user
* @throws UnauthorizedException If the password is invalid or
* the caller does not have permission to make the change
* @throws UserNotFoundException If the given user could not be located
* @throws UnsupportedOperationException If the provider does not
* support the operation (this is an optional operation)
*/
public void updatePassword(String username, String password)
throws UserNotFoundException,
UnauthorizedException,
UnsupportedOperationException;
}
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.messenger.auth;
import org.jivesoftware.util.ClassUtils;
import org.jivesoftware.util.Log;
import org.jivesoftware.messenger.JiveGlobals;
/**
* Provides a centralized source of the various auth providers.<p>
*
* The auth system has one provider. The provider allows you to
* integrate Messenger with various backend authenication systems without
* necessarily replacing the Messenger user management system.
* In other words, you can verify usernames and passwords against a common
* user directory, but allow messenger to manage copies of the user account
* data in it's own database. This results in a redundant storage of data
* and can cause 'data skew' where values are not updated in sync. However,
* it is the simplest and least amount of work to integrate Messenger with
* existing authentication systems.<p>
*
* Users of Jive that wish to change the AuthProvider implementation
* used to generate users can set the <code>AuthProvider.className</code>
* Jive property. For example, if you have altered Jive to use LDAP for
* user information, you'd want to send a custom implementation of
* AuthProvider classes to make LDAP authetnication queries. After changing the
* <code>AuthProvider.className</code> Jive property, you must restart
* Messenger. The valid properties are:<ul>
* <li>AuthProvider.className - specifies an AuthProvider class.</li>
* <li>GroupProvider.className - specifies a GroupProvider class.</li>
* </ul>
*
* @author Iain Shigeoka
*/
public class AuthProviderFactory {
/**
* The default class to instantiate is database implementation.
*/
private static String authClassName =
"org.jivesoftware.messenger.auth.spi.DbAuthProvider";
private static String groupClassName =
"org.jivesoftware.messenger.auth.spi.DbGroupProvider";
private static AuthProvider authProvider = null;
/**
* Returns a concrete AuthProvider. By default, the implementation
* used will be an instance of DbAuthProvider -- the standard database
* implementation that uses the Jive user table. A different authProvider
* can be specified by setting the Jive property "AuthProvider.className".
* However, you must restart Jive for any change to take effect.
*
* @return an AuthProvider instance.
*/
public static AuthProvider getAuthProvider() {
if (authProvider == null) {
// Use className as a convenient object to get a lock on.
synchronized (authClassName) {
if (authProvider == null) {
//See if the classname has been set as a Jive property.
String classNameProp =
JiveGlobals.getProperty("AuthProvider.className");
if (classNameProp != null) {
authClassName = classNameProp;
}
try {
Class c = ClassUtils.forName(authClassName);
authProvider = (AuthProvider)c.newInstance();
}
catch (Exception e) {
Log.error("Exception loading class: " + authClassName, e);
}
}
}
}
return authProvider;
}
}
...@@ -12,32 +12,40 @@ ...@@ -12,32 +12,40 @@
package org.jivesoftware.messenger.auth; package org.jivesoftware.messenger.auth;
/** /**
* Proves that a user has successfully logged in. The existence of an AuthToken object indicates * A token that proves that a user has successfully authenticated.
* that a person has logged in correctly and has authentication to act as the user associated with
* the authentication. An instance of this object can be obtained from the AuthFactory and
* must be passed in to to get an instance of KBFactory or ForumFactory.<p>
* <p/>
* In the case of using the core faq or forum services through a web interface, the expected
* behavior is to have a user login and then store the AuthToken object in their session. In
* some app servers, all objects put in the session must be serializable. The default AuthToken
* implementation obeys this rule, but ensure that custom AuthToken classes do as well.
* *
* @author Iain Shigeoka * @author Matt Tucker
* @see AuthFactory * @see AuthFactory
*/ */
public interface AuthToken { public class AuthToken {
private static final long serialVersionUID = 01L;
private String username;
/**
* Constucts a new AuthToken with the specified username.
*
* @param username the username to create an authToken token with.
*/
public AuthToken(String username) {
this.username = username;
}
/** /**
* Returns the username associated with this AuthToken. * Returns the username associated with this AuthToken.
* *
* @return the username associated with this AuthToken. * @return the username associated with this AuthToken.
*/ */
public String getUsername(); public String getUsername() {
return username;
}
/** /**
* Returns true if this AuthToken is the Anonymous auth token. * Returns true if this AuthToken is the Anonymous auth token.
* *
* @return true if this token is the anonymous AuthToken. * @return true if this token is the anonymous AuthToken.
*/ */
public boolean isAnonymous(); public boolean isAnonymous() {
return username == null;
}
} }
\ No newline at end of file
...@@ -9,13 +9,10 @@ ...@@ -9,13 +9,10 @@
* a copy of which is included in this distribution. * a copy of which is included in this distribution.
*/ */
package org.jivesoftware.messenger.auth.spi; package org.jivesoftware.messenger.auth;
import org.jivesoftware.database.DbConnectionManager; import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.util.Log; import org.jivesoftware.util.Log;
import org.jivesoftware.messenger.auth.AuthFactory;
import org.jivesoftware.messenger.auth.AuthProvider;
import org.jivesoftware.messenger.auth.UnauthorizedException;
import org.jivesoftware.messenger.user.UserNotFoundException; import org.jivesoftware.messenger.user.UserNotFoundException;
import org.jivesoftware.stringprep.Stringprep; import org.jivesoftware.stringprep.Stringprep;
import org.jivesoftware.stringprep.StringprepException; import org.jivesoftware.stringprep.StringprepException;
...@@ -26,49 +23,21 @@ import java.sql.ResultSet; ...@@ -26,49 +23,21 @@ import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
/** /**
* Implements the default Jive authenticator implementation. It makes an * Default AuthProvider implementation. It authenticates against the <tt>jiveUser</tt>
* SQL query to the Jive user table to see if the supplied username and password * database table and supports plain text and digest authentication.
* match a user record. If they do, the appropriate user ID is returned.
* If no matching User record is found an UnauthorizedException is
* thrown.<p>
* *
* Because each call to authenticate() makes a database * Because each call to authenticate() makes a database connection, the
* connection, the results of authentication should be cached whenever possible. When * results of authentication should be cached whenever possible.
* using a servlet or JSP skins, a good method is to cache a token in the
* session named AuthFactory.SESSION_AUTHORIZATION. The default
* AuthFactory.createAuthorization(HttpServletRequest request,
* HttpServletResponse response) method automatically handles this logic.<p>
*
* If you wish to integrate Jive Messenger with your own authentication or
* single sign-on system, you'll need your own implementation of the
* AuthProvider interface. See that interface for further details.<p>
*
* This implementation relies on the DbUserIDProvider to map usernames to IDs in
* order to lookup the password in the jiveUser table. This relationship is not required
* (authentication providers may be entirely independent of user management.
* *
* @author Matt Tucker * @author Matt Tucker
* @author Iain Shigeoka
*/ */
public class DbAuthProvider implements AuthProvider { public class DefaultAuthProvider implements AuthProvider {
private static final String AUTHORIZE = private static final String AUTHORIZE =
"SELECT username FROM jiveUser WHERE username=? AND password=?"; "SELECT username FROM jiveUser WHERE username=? AND password=?";
private static final String SELECT_PASSWORD = private static final String SELECT_PASSWORD =
"SELECT password FROM jiveUser WHERE username=?"; "SELECT password FROM jiveUser WHERE username=?";
private static final String UPDATE_PASSWORD =
"UPDATE jiveUser set password=? WHERE username=?";
/**
* <p>Implementation requires the password to be stored in the user table in plain text.</p>
* <p>You can store the password in the database in another format and modify this method
* to check the given plain-text password (e.g. if the passwords are stored as MD5 hashes,
* MD5 hash the password and compare that with the database).</p>
*
* @param username the username to create an AuthToken with.
* @param password the password to create an AuthToken with.
* @throws UnauthorizedException if the username and password do not match any existing user.
*/
public void authenticate(String username, String password) throws UnauthorizedException { public void authenticate(String username, String password) throws UnauthorizedException {
if (username == null || password == null) { if (username == null || password == null) {
throw new UnauthorizedException(); throw new UnauthorizedException();
...@@ -86,10 +55,8 @@ public class DbAuthProvider implements AuthProvider { ...@@ -86,10 +55,8 @@ public class DbAuthProvider implements AuthProvider {
pstmt = con.prepareStatement(AUTHORIZE); pstmt = con.prepareStatement(AUTHORIZE);
pstmt.setString(1, username); pstmt.setString(1, username);
pstmt.setString(2, password); pstmt.setString(2, password);
ResultSet rs = pstmt.executeQuery(); ResultSet rs = pstmt.executeQuery();
// If the query has no results, the username and password
// If the query had no results, the username and password
// did not match a user record. Therefore, throw an exception. // did not match a user record. Therefore, throw an exception.
if (!rs.next()) { if (!rs.next()) {
throw new UnauthorizedException(); throw new UnauthorizedException();
...@@ -109,18 +76,6 @@ public class DbAuthProvider implements AuthProvider { ...@@ -109,18 +76,6 @@ public class DbAuthProvider implements AuthProvider {
// Got this far, so the user must be authorized. // Got this far, so the user must be authorized.
} }
/**
* Implementation requires the password to be stored in the user table in plain text.<p>
*
* There is no way to support digest authentication without the plain text password in the
* database. If you can't support plain-text passwords in the backend, then always throw
* UnauthorizedException immediately and be sure to return false in isDigestSupported().
*
* @param username The username of the user to check
* @param token The unique token (XMPP stream id) used to generate the digest
* @param digest The digest to be checked
* @throws UnauthorizedException If the login attempt failed
*/
public void authenticate(String username, String token, String digest) throws UnauthorizedException { public void authenticate(String username, String token, String digest) throws UnauthorizedException {
if (username == null || token == null || digest == null) { if (username == null || token == null || digest == null) {
throw new UnauthorizedException(); throw new UnauthorizedException();
...@@ -146,7 +101,7 @@ public class DbAuthProvider implements AuthProvider { ...@@ -146,7 +101,7 @@ public class DbAuthProvider implements AuthProvider {
throw new UnauthorizedException(); throw new UnauthorizedException();
} }
String pass = rs.getString(1); String pass = rs.getString(1);
String anticipatedDigest = AuthFactory.createTokenPasswordDigest(token, pass); String anticipatedDigest = AuthFactory.createDigest(token, pass);
if (!digest.equalsIgnoreCase(anticipatedDigest)) { if (!digest.equalsIgnoreCase(anticipatedDigest)) {
throw new UnauthorizedException(); throw new UnauthorizedException();
} }
...@@ -165,46 +120,6 @@ public class DbAuthProvider implements AuthProvider { ...@@ -165,46 +120,6 @@ public class DbAuthProvider implements AuthProvider {
// Got this far, so the user must be authorized. // Got this far, so the user must be authorized.
} }
/**
* Update the password for the given user. If you don't want people to change their
* password through Messenger just throw UnauthorizedException.
*
* @param username The username of the user who's password is changing
* @param password The new password for the user
* @throws UnauthorizedException If the password is invalid or the provider doesn't allow password updates
* @throws UserNotFoundException If the given user could not be located
*/
public void updatePassword(String username, String password) throws UserNotFoundException, UnauthorizedException {
if (username == null || password == null) {
throw new UnauthorizedException();
}
try {
username = Stringprep.nodeprep(username);
}
catch (StringprepException se) {
throw new UnauthorizedException("Illegal username: " + se.getMessage());
}
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(UPDATE_PASSWORD);
pstmt.setString(1, password);
pstmt.setString(2, username);
pstmt.executeUpdate();
}
catch (SQLException e) {
Log.error("Exception in DbAuthProvider", e);
throw new UnauthorizedException();
}
finally {
try { if (pstmt != null) pstmt.close(); }
catch (Exception e) { Log.error(e); }
try { if (con != null) con.close(); }
catch (Exception e) { Log.error(e); }
}
}
public boolean isPlainSupported() { public boolean isPlainSupported() {
return true; return true;
} }
......
...@@ -11,53 +11,26 @@ ...@@ -11,53 +11,26 @@
package org.jivesoftware.messenger.auth; package org.jivesoftware.messenger.auth;
import java.io.PrintStream;
import java.io.PrintWriter;
/** /**
* Thrown if a User does not have permission to access a particular method. * Thrown if a user does not have permission to access a particular method.
* *
* @author Iain Shigeoka * @author Iain Shigeoka
*/ */
public class UnauthorizedException extends Exception { public class UnauthorizedException extends Exception {
private Throwable nestedThrowable = null;
public UnauthorizedException() { public UnauthorizedException() {
super(); super();
} }
public UnauthorizedException(String msg) { public UnauthorizedException(String message) {
super(msg); super(message);
}
public UnauthorizedException(Throwable nestedThrowable) {
this.nestedThrowable = nestedThrowable;
}
public UnauthorizedException(String msg, Throwable nestedThrowable) {
super(msg);
this.nestedThrowable = nestedThrowable;
}
public void printStackTrace() {
super.printStackTrace();
if (nestedThrowable != null) {
nestedThrowable.printStackTrace();
}
} }
public void printStackTrace(PrintStream ps) { public UnauthorizedException(Throwable cause) {
super.printStackTrace(ps); super(cause);
if (nestedThrowable != null) {
nestedThrowable.printStackTrace(ps);
}
} }
public void printStackTrace(PrintWriter pw) { public UnauthorizedException(String message, Throwable cause) {
super.printStackTrace(pw); super(message, cause);
if (nestedThrowable != null) {
nestedThrowable.printStackTrace(pw);
}
} }
} }
\ No newline at end of file
...@@ -3,20 +3,7 @@ ...@@ -3,20 +3,7 @@
<head> <head>
</head> </head>
<body> <body>
<p>Provides the interfaces and classes necessary to create custom <p>Authentication service interfaces and classes. Custom authentication
authentication data providers for Messenger.</p> implementations can be created by extending the {@link AuthProvider} interface.
<p>Authentication can be separately provided from user accounts and is
only used during initial XMPP session connection (an infrequent
operation considering the persistent nature of most XMPP connections).
Implementors should concentrate on the AuthProvider interface.</p>
<p>Messenger also includes the concept of user groups, used to assign
permissions to sets of users rather than having to assign permissions
to many individuals which can be tedious with large user populations.
The group concept is heavily used in Jive Forums upon which the
Messenger user system is based. Messenger 1.1 does not actively use
groups but group support was kept in Messenger to ease future
integration with Forums. Custom data provider implementors are
discouraged from implementing custom GroupProvider classes as the
interface may change.</p>
</body> </body>
</html> </html>
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.messenger.auth.spi;
import org.jivesoftware.messenger.auth.AuthToken;
import java.io.Serializable;
/**
* Database implementation of the AuthToken interface.
*
* @author Iain Shigeoka
*/
public final class AuthTokenImpl implements AuthToken, Serializable {
private static final long serialVersionUID = 01L;
private String username;
/**
* Constucts a new AuthTokenImpl with the specified username.
*
* @param username the username to create an authToken token with.
*/
public AuthTokenImpl(String username) {
this.username = username;
}
// AuthToken Interface
public String getUsername() {
return username;
}
public boolean isAnonymous() {
return username == null;
}
}
\ No newline at end of file
...@@ -28,7 +28,7 @@ import java.util.Collection; ...@@ -28,7 +28,7 @@ import java.util.Collection;
* *
* @author Iain Shigeoka * @author Iain Shigeoka
*/ */
public class DbGroupProvider implements GroupProvider { public class DefaultGroupProvider implements GroupProvider {
private static final String INSERT_GROUP = private static final String INSERT_GROUP =
"INSERT INTO jiveGroup (groupName, description) VALUES (?, ?)"; "INSERT INTO jiveGroup (groupName, description) VALUES (?, ?)";
......
...@@ -11,9 +11,6 @@ ...@@ -11,9 +11,6 @@
package org.jivesoftware.messenger.group; package org.jivesoftware.messenger.group;
import java.io.PrintStream;
import java.io.PrintWriter;
/** /**
* Thrown when attempting to create a group that already exists. * Thrown when attempting to create a group that already exists.
* *
...@@ -21,43 +18,20 @@ import java.io.PrintWriter; ...@@ -21,43 +18,20 @@ import java.io.PrintWriter;
*/ */
public class GroupAlreadyExistsException extends Exception { public class GroupAlreadyExistsException extends Exception {
private Throwable nestedThrowable = null;
public GroupAlreadyExistsException() { public GroupAlreadyExistsException() {
super(); super();
} }
public GroupAlreadyExistsException(String msg) { public GroupAlreadyExistsException(String message) {
super(msg); super(message);
}
public GroupAlreadyExistsException(Throwable nestedThrowable) {
this.nestedThrowable = nestedThrowable;
}
public GroupAlreadyExistsException(String msg, Throwable nestedThrowable) {
super(msg);
this.nestedThrowable = nestedThrowable;
}
public void printStackTrace() {
super.printStackTrace();
if (nestedThrowable != null) {
nestedThrowable.printStackTrace();
}
} }
public void printStackTrace(PrintStream ps) { public GroupAlreadyExistsException(Throwable cause) {
super.printStackTrace(ps); super(cause);
if (nestedThrowable != null) {
nestedThrowable.printStackTrace(ps);
}
} }
public void printStackTrace(PrintWriter pw) { public GroupAlreadyExistsException(String message, Throwable cause) {
super.printStackTrace(pw); super(message, cause);
if (nestedThrowable != null) {
nestedThrowable.printStackTrace(pw);
}
} }
} }
\ No newline at end of file
...@@ -22,7 +22,7 @@ import java.util.Collection; ...@@ -22,7 +22,7 @@ import java.util.Collection;
import java.util.ArrayList; import java.util.ArrayList;
/** /**
* Manages groups * Manages groups.
* *
* @see Group * @see Group
* @author Matt Tucker * @author Matt Tucker
...@@ -51,15 +51,15 @@ public class GroupManager { ...@@ -51,15 +51,15 @@ public class GroupManager {
groupCache = CacheManager.getCache("group"); groupCache = CacheManager.getCache("group");
groupMemberCache = CacheManager.getCache("group member"); groupMemberCache = CacheManager.getCache("group member");
// Load a group provider. // Load a group provider.
String className = JiveGlobals.getXMLProperty("group.provider.className", String className = JiveGlobals.getXMLProperty("provider.group.className",
"org.jivesoftware.messenger.group.DbGroupProvider"); "org.jivesoftware.messenger.group.DefaultGroupProvider");
try { try {
Class c = ClassUtils.forName(className); Class c = ClassUtils.forName(className);
provider = (GroupProvider)c.newInstance(); provider = (GroupProvider)c.newInstance();
} }
catch (Exception e) { catch (Exception e) {
Log.error("Error loading group provider: " + className, e); Log.error("Error loading group provider: " + className, e);
provider = new DbGroupProvider(); provider = new DefaultGroupProvider();
} }
} }
......
...@@ -16,8 +16,17 @@ import org.jivesoftware.messenger.user.User; ...@@ -16,8 +16,17 @@ import org.jivesoftware.messenger.user.User;
import java.util.Collection; import java.util.Collection;
/** /**
* Group providers load and store group information from a back-end store * Provider interface for groups. Users that wish to integrate with
* such as a database table, LDAP, etc. * their own group system must implement this class and then register
* the implementation with Jive Messenger in the <tt>jive-messenger.xml</tt>
* file. An entry in that file would look like the following:
*
* <pre>
* &lt;provider&gt;
* &lt;group&gt;
* &lt;className&gt;com.foo.auth.CustomGroupProvider&lt;/className&gt;
* &lt;/group&gt;
* &lt;/provider&gt;</pre>
* *
* @author Matt Tucker * @author Matt Tucker
*/ */
......
...@@ -178,10 +178,11 @@ public class IQAuthHandler extends IQHandler implements IQAuthInfo { ...@@ -178,10 +178,11 @@ public class IQAuthHandler extends IQHandler implements IQAuthInfo {
if (response == null) { if (response == null) {
AuthToken token = null; AuthToken token = null;
if (password != null && AuthFactory.isPlainSupported()) { if (password != null && AuthFactory.isPlainSupported()) {
token = AuthFactory.getAuthToken(username, password); token = AuthFactory.authenticate(username, password);
} }
else if (digest != null && AuthFactory.isDigestSupported()) { else if (digest != null && AuthFactory.isDigestSupported()) {
token = AuthFactory.getAuthToken(username, session.getStreamID().toString(), digest); token = AuthFactory.authenticate(username, session.getStreamID().toString(),
digest);
} }
if (token == null) { if (token == null) {
throw new UnauthorizedException(); throw new UnauthorizedException();
......
...@@ -18,7 +18,8 @@ import org.jivesoftware.messenger.forms.spi.XDataFormImpl; ...@@ -18,7 +18,8 @@ import org.jivesoftware.messenger.forms.spi.XDataFormImpl;
import org.jivesoftware.messenger.forms.spi.XFormFieldImpl; import org.jivesoftware.messenger.forms.spi.XFormFieldImpl;
import org.jivesoftware.util.Log; import org.jivesoftware.util.Log;
import org.jivesoftware.messenger.*; import org.jivesoftware.messenger.*;
import org.jivesoftware.messenger.auth.Permissions; import org.jivesoftware.messenger.Permissions;
import org.jivesoftware.messenger.roster.RosterManager;
import org.jivesoftware.messenger.auth.UnauthorizedException; import org.jivesoftware.messenger.auth.UnauthorizedException;
import org.jivesoftware.messenger.user.*; import org.jivesoftware.messenger.user.*;
...@@ -163,7 +164,7 @@ public class IQRegisterHandler extends IQHandler implements ServerFeaturesProvid ...@@ -163,7 +164,7 @@ public class IQRegisterHandler extends IQHandler implements ServerFeaturesProvid
currentRegistration.addElement("registered"); currentRegistration.addElement("registered");
currentRegistration.element("username").setText(user.getUsername()); currentRegistration.element("username").setText(user.getUsername());
currentRegistration.element("password").setText(""); currentRegistration.element("password").setText("");
currentRegistration.element("email").setText(user.getInfo().getEmail()); currentRegistration.element("email").setText(user.getEmail());
Element form = currentRegistration.element(QName.get("x", "jabber:x:data")); Element form = currentRegistration.element(QName.get("x", "jabber:x:data"));
Iterator fields = form.elementIterator("field"); Iterator fields = form.elementIterator("field");
...@@ -174,10 +175,10 @@ public class IQRegisterHandler extends IQHandler implements ServerFeaturesProvid ...@@ -174,10 +175,10 @@ public class IQRegisterHandler extends IQHandler implements ServerFeaturesProvid
field.addElement("value").addText(user.getUsername()); field.addElement("value").addText(user.getUsername());
} }
else if ("name".equals(field.attributeValue("var"))) { else if ("name".equals(field.attributeValue("var"))) {
field.addElement("value").addText(user.getInfo().getName()); field.addElement("value").addText(user.getName());
} }
else if ("email".equals(field.attributeValue("var"))) { else if ("email".equals(field.attributeValue("var"))) {
field.addElement("value").addText(user.getInfo().getEmail()); field.addElement("value").addText(user.getEmail());
} }
} }
reply.setChildElement(currentRegistration); reply.setChildElement(currentRegistration);
...@@ -279,17 +280,12 @@ public class IQRegisterHandler extends IQHandler implements ServerFeaturesProvid ...@@ -279,17 +280,12 @@ public class IQRegisterHandler extends IQHandler implements ServerFeaturesProvid
if (user != null) { if (user != null) {
if (user.getUsername().equalsIgnoreCase(username)) { if (user.getUsername().equalsIgnoreCase(username)) {
user.setPassword(password); user.setPassword(password);
user.getInfo().setEmail(email); user.setEmail(email);
newUser = user; newUser = user;
} }
else { else {
// An admin can create new accounts when logged in. // An admin can create new accounts when logged in.
if (user.isAuthorized(Permissions.SYSTEM_ADMIN)) { newUser = userManager.createUser(username, password, null, email);
newUser = userManager.createUser(username, password, email);
}
else {
throw new UnauthorizedException();
}
} }
} }
else { else {
...@@ -297,9 +293,9 @@ public class IQRegisterHandler extends IQHandler implements ServerFeaturesProvid ...@@ -297,9 +293,9 @@ public class IQRegisterHandler extends IQHandler implements ServerFeaturesProvid
} }
} }
else { else {
newUser = userManager.createUser(username, password, email); newUser = userManager.createUser(username, password, null, email);
} }
// Set and save the extra user info (e.g. Full name, name visible, etc.) // Set and save the extra user info (e.g. full name, etc.)
if (newUser != null && registrationForm != null) { if (newUser != null && registrationForm != null) {
Iterator<String> values; Iterator<String> values;
// Get the full name sent in the form // Get the full name sent in the form
...@@ -307,9 +303,8 @@ public class IQRegisterHandler extends IQHandler implements ServerFeaturesProvid ...@@ -307,9 +303,8 @@ public class IQRegisterHandler extends IQHandler implements ServerFeaturesProvid
if (field != null) { if (field != null) {
values = field.getValues(); values = field.getValues();
String name = (values.hasNext() ? values.next() : " "); String name = (values.hasNext() ? values.next() : " ");
newUser.getInfo().setName(name); newUser.setName(name);
} }
newUser.saveInfo();
} }
reply = IQ.createResultIQ(packet); reply = IQ.createResultIQ(packet);
......
...@@ -15,10 +15,12 @@ import org.jivesoftware.messenger.disco.ServerFeaturesProvider; ...@@ -15,10 +15,12 @@ import org.jivesoftware.messenger.disco.ServerFeaturesProvider;
import org.jivesoftware.util.LocaleUtils; import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log; import org.jivesoftware.util.Log;
import org.jivesoftware.messenger.*; import org.jivesoftware.messenger.*;
import org.jivesoftware.messenger.roster.*;
import org.jivesoftware.messenger.auth.UnauthorizedException; import org.jivesoftware.messenger.auth.UnauthorizedException;
import org.jivesoftware.messenger.user.*; import org.jivesoftware.messenger.user.*;
import org.jivesoftware.messenger.user.Roster; import org.jivesoftware.messenger.roster.Roster;
import org.xmpp.packet.*; import org.xmpp.packet.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
...@@ -204,7 +206,7 @@ public class IQRosterHandler extends IQHandler implements ServerFeaturesProvider ...@@ -204,7 +206,7 @@ public class IQRosterHandler extends IQHandler implements ServerFeaturesProvider
* @param sender The JID of the sender of the removal request * @param sender The JID of the sender of the removal request
* @param item The removal item element * @param item The removal item element
*/ */
private void removeItem(Roster roster, JID sender, org.xmpp.packet.Roster.Item item) private void removeItem(org.jivesoftware.messenger.roster.Roster roster, JID sender, org.xmpp.packet.Roster.Item item)
throws UnauthorizedException throws UnauthorizedException
{ {
......
...@@ -19,6 +19,8 @@ import org.jivesoftware.messenger.user.UserNotFoundException; ...@@ -19,6 +19,8 @@ import org.jivesoftware.messenger.user.UserNotFoundException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Collection;
import org.dom4j.DocumentHelper; import org.dom4j.DocumentHelper;
import org.dom4j.Element; import org.dom4j.Element;
import org.dom4j.QName; import org.dom4j.QName;
...@@ -90,12 +92,12 @@ public class IQvCardHandler extends IQHandler { ...@@ -90,12 +92,12 @@ public class IQvCardHandler extends IQHandler {
Element vcard = DocumentHelper.createElement(QName.get("vCard", "vcard-temp")); Element vcard = DocumentHelper.createElement(QName.get("vCard", "vcard-temp"));
result.setChildElement(vcard); result.setChildElement(vcard);
Iterator names = user.getVCardPropertyNames(); VCardManager vManager = VCardManager.getInstance();
while (names.hasNext()) { Collection<String> names = vManager.getVCardPropertyNames(user.getUsername());
String name = (String)names.next(); for (String name : names) {
String path = name.replace(':', '/'); String path = name.replace(':', '/');
Element node = DocumentHelper.makeElement(vcard, path); Element node = DocumentHelper.makeElement(vcard, path);
node.setText(user.getVCardProperty(name)); node.setText(vManager.getVCardProperty(user.getUsername(), name));
} }
} }
else { else {
...@@ -125,7 +127,8 @@ public class IQvCardHandler extends IQHandler { ...@@ -125,7 +127,8 @@ public class IQvCardHandler extends IQHandler {
String value = child.getTextTrim(); String value = child.getTextTrim();
if (value != null) { if (value != null) {
if (!"".equals(value)) { if (!"".equals(value)) {
user.setVCardProperty(createName(nameStack), value); VCardManager.getInstance().setVCardProperty(user.getUsername(),
createName(nameStack), value);
} }
} }
readVCard(child, nameStack, user); readVCard(child, nameStack, user);
......
...@@ -16,9 +16,11 @@ import org.jivesoftware.messenger.container.BasicModule; ...@@ -16,9 +16,11 @@ import org.jivesoftware.messenger.container.BasicModule;
import org.jivesoftware.util.LocaleUtils; import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log; import org.jivesoftware.util.Log;
import org.jivesoftware.messenger.*; import org.jivesoftware.messenger.*;
import org.jivesoftware.messenger.roster.Roster;
import org.jivesoftware.messenger.roster.RosterItem;
import org.jivesoftware.messenger.auth.UnauthorizedException; import org.jivesoftware.messenger.auth.UnauthorizedException;
import org.jivesoftware.messenger.user.*; import org.jivesoftware.messenger.user.*;
import org.jivesoftware.messenger.user.spi.CachedRosterImpl; import org.jivesoftware.messenger.roster.spi.CachedRosterImpl;
import org.xmpp.packet.Presence; import org.xmpp.packet.Presence;
import org.xmpp.packet.Packet; import org.xmpp.packet.Packet;
import org.xmpp.packet.JID; import org.xmpp.packet.JID;
......
...@@ -15,11 +15,13 @@ import org.jivesoftware.messenger.container.BasicModule; ...@@ -15,11 +15,13 @@ import org.jivesoftware.messenger.container.BasicModule;
import org.jivesoftware.util.LocaleUtils; import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log; import org.jivesoftware.util.Log;
import org.jivesoftware.messenger.*; import org.jivesoftware.messenger.*;
import org.jivesoftware.messenger.roster.RosterItem;
import org.jivesoftware.messenger.auth.UnauthorizedException; import org.jivesoftware.messenger.auth.UnauthorizedException;
import org.jivesoftware.messenger.spi.SessionImpl; import org.jivesoftware.messenger.spi.SessionImpl;
import org.jivesoftware.messenger.user.CachedRoster; import org.jivesoftware.messenger.roster.CachedRoster;
import org.jivesoftware.messenger.user.RosterItem; import org.jivesoftware.messenger.roster.RosterItem;
import org.jivesoftware.messenger.user.RosterManager; import org.jivesoftware.messenger.roster.RosterManager;
import org.jivesoftware.messenger.roster.CachedRoster;
import org.jivesoftware.messenger.user.UserNotFoundException; import org.jivesoftware.messenger.user.UserNotFoundException;
import org.xmpp.packet.*; import org.xmpp.packet.*;
......
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.messenger.ldap;
import org.jivesoftware.messenger.auth.UnauthorizedException;
import org.jivesoftware.messenger.user.UserInfo;
import org.jivesoftware.messenger.user.UserInfoProvider;
import org.jivesoftware.messenger.user.UserNotFoundException;
import org.jivesoftware.messenger.user.UserInfo;
import org.jivesoftware.util.Log;
import java.util.Date;
import javax.naming.directory.*;
import javax.naming.NamingException;
/**
* LDAP implementation of the UserInfoProvider interface. The LdapUserIDProvider
* can operate in two modes -- in the pure LDAP mode, all user data is stored in
* the LDAP store. This mode generally requires modifications to the LDAP schema
* to accommodate data that Messenger needs. In the mixed mode, data that Messenger
* needs is stored locally.
*
* @author Jim Berrettini
*/
public class LdapUserInfoProvider implements UserInfoProvider {
private LdapManager manager;
/**
* Constructor initializes the internal LdapManager instance.
*/
public LdapUserInfoProvider() {
manager = LdapManager.getInstance();
}
/**
* <p>Obtain the UserInfo of a user. Will retrieve either from LDAP or locally, depending on mode of operation.</p>
*
* @param username the username.
* @return a user info object.
* @throws UserNotFoundException
*/
public UserInfo getInfo(String username) throws UserNotFoundException {
return getInfoFromLdap(username);
}
/**
* <p>Sets the user's info. In pure LDAP mode, this is unsupported.</p>
*
* @param username username for setting info.
* @param info to set.
* @throws UserNotFoundException
* @throws UnauthorizedException
* @throws UnsupportedOperationException
*/
public void setInfo(String username, UserInfo info)
throws UserNotFoundException, UnauthorizedException, UnsupportedOperationException {
throw new UnsupportedOperationException("All LDAP mode: Cannot modify data in LDAP.");
}
/**
* Pure LDAP method for getting info for a given userID.
*
* @param username the username.
* @return UserInfo for that user.
* @throws UserNotFoundException
*/
private UserInfo getInfoFromLdap(String username) throws UserNotFoundException {
UserInfo userInfo = null;
DirContext ctx = null;
try {
String userDN = manager.findUserDN(username);
// Load record.
String[] attributes = new String[]{
manager.getUsernameField(), manager.getNameField(),
manager.getEmailField()
};
ctx = manager.getContext();
Attributes attrs = ctx.getAttributes(userDN, attributes);
String name = null;
String email = null;
Attribute nameField = attrs.get(manager.getNameField());
if (nameField != null) {
name = (String)nameField.get();
}
Attribute emailField = attrs.get(manager.getEmailField());
if (emailField != null) {
email = (String)emailField.get();
}
userInfo = new UserInfo(username, name, email, new Date(), new Date());
}
catch (Exception e) {
throw new UserNotFoundException(e);
}
finally {
try { ctx.close(); }
catch (Exception ignored) { }
}
return userInfo;
}
}
\ No newline at end of file
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.messenger.ldap;
import org.jivesoftware.messenger.user.UserPropertiesProvider;
import java.util.Collections;
import java.util.Map;
/**
* <p>Implement this provider to store user and vcard properties somewhere
* other than the Jive tables, or to capture jive property events. Currently this is unimplemented.</p>
*
* @author Jim Berrettini
*/
public class LdapUserPropertiesProvider implements UserPropertiesProvider {
public void deleteVcardProperty(String username, String name) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
public void deleteUserProperty(String username, String name) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
public void insertVcardProperty(String username, String name, String value) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
public void insertUserProperty(String username, String name, String value) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
public void updateVcardProperty(String username, String name, String value) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
public void updateUserProperty(String username, String name, String value) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
public Map getVcardProperties(String username) {
return Collections.EMPTY_MAP;
}
public Map getUserProperties(String username) {
return Collections.EMPTY_MAP;
}
}
\ No newline at end of file
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.messenger.ldap;
import org.jivesoftware.messenger.user.*;
import org.jivesoftware.util.Log;
import javax.naming.directory.*;
import javax.naming.NamingEnumeration;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
/**
* LDAP implementation of the UserProvider interface. All data in the directory is
* treated as read-only so any set operations will result in an exception.
*
* @author Matt Tucker
*/
public class LdapUserProvider implements UserProvider {
private LdapManager manager;
public LdapUserProvider() {
manager = LdapManager.getInstance();
}
public User loadUser(String username) throws UserNotFoundException {
DirContext ctx = null;
try {
String userDN = manager.findUserDN(username);
// Load record.
String[] attributes = new String[]{
manager.getUsernameField(), manager.getNameField(),
manager.getEmailField()
};
ctx = manager.getContext();
Attributes attrs = ctx.getAttributes(userDN, attributes);
String name = null;
String email = null;
Attribute nameField = attrs.get(manager.getNameField());
if (nameField != null) {
name = (String)nameField.get();
}
Attribute emailField = attrs.get(manager.getEmailField());
if (emailField != null) {
email = (String)emailField.get();
}
return new User(username, name, email, new Date(), new Date());
}
catch (Exception e) {
throw new UserNotFoundException(e);
}
finally {
try { ctx.close(); }
catch (Exception ignored) { }
}
}
public User createUser(String username, String password, String name, String email)
throws UserAlreadyExistsException
{
throw new UnsupportedOperationException();
}
public void deleteUser(String username) {
throw new UnsupportedOperationException();
}
public int getUserCount() {
int count = 0;
DirContext ctx = null;
try {
ctx = manager.getContext();
// Search for the dn based on the username.
SearchControls constraints = new SearchControls();
constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
constraints.setReturningAttributes(new String[] { manager.getUsernameField() });
String filter = "(" + manager.getUsernameField() + "=*)";
NamingEnumeration answer = ctx.search("", filter, constraints);
while (answer.hasMoreElements()) {
count++;
answer.nextElement();
}
}
catch (Exception e) {
Log.error(e);
}
finally {
try { ctx.close(); }
catch (Exception ignored) { }
}
return count;
}
public Collection<User> getUsers() {
List<String> usernames = new ArrayList<String>();
DirContext ctx = null;
try {
ctx = manager.getContext();
// Search for the dn based on the username.
SearchControls constraints = new SearchControls();
constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
constraints.setReturningAttributes(new String[] { manager.getUsernameField() });
String filter = "(" + manager.getUsernameField() + "=*)";
NamingEnumeration answer = ctx.search("", filter, constraints);
while (answer.hasMoreElements()) {
// Get the next userID.
usernames.add(
(String)((SearchResult)answer.next()).getAttributes().get(
manager.getUsernameField()).get()
);
}
}
catch (Exception e) {
Log.error(e);
}
finally {
try { ctx.close(); }
catch (Exception ignored) { }
}
return new UserCollection((String[])usernames.toArray(new String[usernames.size()]));
}
public Collection<User> getUsers(int startIndex, int numResults) {
List<String> usernames = new ArrayList<String>();
DirContext ctx = null;
try {
ctx = manager.getContext();
// Search for the dn based on the username.
SearchControls constraints = new SearchControls();
constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
constraints.setReturningAttributes(new String[] { "jiveUserID" });
String filter = "(jiveUserID=*)";
NamingEnumeration answer = ctx.search("", filter, constraints);
for (int i = 0; i < startIndex; i++) {
answer.next();
}
// Now read in desired number of results (or stop if we run out of results).
for (int i = 0; i < numResults; i++) {
if (answer.hasMoreElements()) {
// Get the next userID.
usernames.add(
(String)((SearchResult)answer.next()).getAttributes().get(
"jiveUserID").get()
);
}
else {
break;
}
}
}
catch (Exception e) {
Log.error(e);
}
finally {
try { ctx.close(); }
catch (Exception ignored) { }
}
return new UserCollection((String[])usernames.toArray(new String[usernames.size()]));
}
public void setPassword(String username, String password) throws UserNotFoundException {
throw new UnsupportedOperationException();
}
public void setName(String username, String name) throws UserNotFoundException {
throw new UnsupportedOperationException();
}
public void setEmail(String username, String email) throws UserNotFoundException {
throw new UnsupportedOperationException();
}
public void setCreationDate(String username, Date creationDate) throws UserNotFoundException {
throw new UnsupportedOperationException();
}
public void setModificationDate(String username, Date modificationDate) throws UserNotFoundException {
throw new UnsupportedOperationException();
}
}
\ No newline at end of file
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
* a copy of which is included in this distribution. * a copy of which is included in this distribution.
*/ */
package org.jivesoftware.messenger.user; package org.jivesoftware.messenger.roster;
import java.util.Hashtable; import java.util.Hashtable;
import java.util.Iterator; import java.util.Iterator;
...@@ -18,6 +18,10 @@ import java.util.List; ...@@ -18,6 +18,10 @@ import java.util.List;
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.jivesoftware.messenger.auth.UnauthorizedException; import org.jivesoftware.messenger.auth.UnauthorizedException;
import org.jivesoftware.messenger.roster.Roster;
import org.jivesoftware.messenger.roster.RosterItem;
import org.jivesoftware.messenger.user.UserNotFoundException;
import org.jivesoftware.messenger.user.UserAlreadyExistsException;
import org.jivesoftware.util.CacheSizes; import org.jivesoftware.util.CacheSizes;
import org.jivesoftware.util.Cacheable; import org.jivesoftware.util.Cacheable;
import org.xmpp.packet.JID; import org.xmpp.packet.JID;
......
...@@ -9,12 +9,13 @@ ...@@ -9,12 +9,13 @@
* a copy of which is included in this distribution. * a copy of which is included in this distribution.
*/ */
package org.jivesoftware.messenger.user; package org.jivesoftware.messenger.roster;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import org.xmpp.packet.JID; import org.xmpp.packet.JID;
import org.jivesoftware.messenger.roster.RosterItem;
/** /**
* <p>Implements the basic RosterItem interface storing all data into simple fields.</p> * <p>Implements the basic RosterItem interface storing all data into simple fields.</p>
......
...@@ -9,11 +9,13 @@ ...@@ -9,11 +9,13 @@
* a copy of which is included in this distribution. * a copy of which is included in this distribution.
*/ */
package org.jivesoftware.messenger.user; package org.jivesoftware.messenger.roster;
import org.jivesoftware.messenger.auth.UnauthorizedException; import org.jivesoftware.messenger.auth.UnauthorizedException;
import org.jivesoftware.messenger.roster.*;
import org.jivesoftware.util.Cacheable; import org.jivesoftware.util.Cacheable;
import org.xmpp.packet.*; import org.xmpp.packet.*;
import org.xmpp.packet.Roster;
/** /**
* <p>A Roster that is cached in memory and persisted to some backend storage system.</p> * <p>A Roster that is cached in memory and persisted to some backend storage system.</p>
...@@ -25,7 +27,7 @@ import org.xmpp.packet.*; ...@@ -25,7 +27,7 @@ import org.xmpp.packet.*;
* <p/> * <p/>
* *
*/ */
public interface CachedRoster extends Roster, Cacheable { public interface CachedRoster extends org.jivesoftware.messenger.roster.Roster, Cacheable {
/** /**
* <p>Return the username of the user or chatbot that owns this roster.</p> * <p>Return the username of the user or chatbot that owns this roster.</p>
......
...@@ -9,9 +9,10 @@ ...@@ -9,9 +9,10 @@
* a copy of which is included in this distribution. * a copy of which is included in this distribution.
*/ */
package org.jivesoftware.messenger.user; package org.jivesoftware.messenger.roster;
import org.jivesoftware.util.Cacheable; import org.jivesoftware.util.Cacheable;
import org.jivesoftware.messenger.roster.RosterItem;
/** /**
* <p>Represents a persistently stored roster item.</p> * <p>Represents a persistently stored roster item.</p>
......
...@@ -9,9 +9,11 @@ ...@@ -9,9 +9,11 @@
* a copy of which is included in this distribution. * a copy of which is included in this distribution.
*/ */
package org.jivesoftware.messenger.user; package org.jivesoftware.messenger.roster;
import org.jivesoftware.messenger.auth.UnauthorizedException; import org.jivesoftware.messenger.auth.UnauthorizedException;
import org.jivesoftware.messenger.user.UserNotFoundException;
import org.jivesoftware.messenger.user.UserAlreadyExistsException;
import org.xmpp.packet.JID; import org.xmpp.packet.JID;
import java.util.Iterator; import java.util.Iterator;
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
* a copy of which is included in this distribution. * a copy of which is included in this distribution.
*/ */
package org.jivesoftware.messenger.user; package org.jivesoftware.messenger.roster;
import org.jivesoftware.messenger.auth.UnauthorizedException; import org.jivesoftware.messenger.auth.UnauthorizedException;
import org.jivesoftware.util.IntEnum; import org.jivesoftware.util.IntEnum;
......
...@@ -9,39 +9,66 @@ ...@@ -9,39 +9,66 @@
* a copy of which is included in this distribution. * a copy of which is included in this distribution.
*/ */
package org.jivesoftware.messenger.user.spi; package org.jivesoftware.messenger.roster;
import java.sql.Connection; import org.jivesoftware.messenger.roster.spi.CachedRosterItemImpl;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.database.SequenceManager;
import org.jivesoftware.messenger.user.CachedRosterItem;
import org.jivesoftware.messenger.user.RosterItem;
import org.jivesoftware.messenger.user.RosterItemProvider;
import org.jivesoftware.messenger.user.UserAlreadyExistsException; import org.jivesoftware.messenger.user.UserAlreadyExistsException;
import org.jivesoftware.messenger.user.UserNotFoundException; import org.jivesoftware.messenger.user.UserNotFoundException;
import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.database.SequenceManager;
import org.jivesoftware.util.JiveConstants; import org.jivesoftware.util.JiveConstants;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log; import org.jivesoftware.util.Log;
import org.jivesoftware.util.LocaleUtils;
import org.xmpp.packet.JID; import org.xmpp.packet.JID;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.ResultSet;
/** /**
* <p>Implements the roster item provider against the jiveRoster table * <p>Defines the provider methods required for creating, reading, updating and deleting roster items.</p>
* using standard Jive default JDBC connections.</p> * <p/>
* <p>Rosters are another user resource accessed via the user or chatbot's long ID. A user/chatbot may have
* zero or more roster items and each roster item may have zero or more groups. Each roster item is
* additionaly keyed on a XMPP jid. In most cases, the entire roster will be read in from memory and manipulated
* or sent to the user. However some operations will need to retrive specific roster items rather than the
* entire roster.</p>
* *
* @author Iain Shigeoka * @author Iain Shigeoka
*/ */
public class DbRosterItemProvider implements RosterItemProvider { public class RosterItemProvider {
private static final String CREATE_ROSTER_ITEM = private static final String CREATE_ROSTER_ITEM =
"INSERT INTO jiveRoster (username, rosterID, jid, sub, ask, recv, nick) " + "INSERT INTO jiveRoster (username, rosterID, jid, sub, ask, recv, nick) " +
"VALUES (?, ?, ?, ?, ?, ?, ?)"; "VALUES (?, ?, ?, ?, ?, ?, ?)";
private static final String UPDATE_ROSTER_ITEM =
"UPDATE jiveRoster SET sub=?, ask=?, recv=?, nick=? WHERE rosterID=?";
private static final String DELETE_ROSTER_ITEM_GROUPS =
"DELETE FROM jiveRosterGroups WHERE rosterID=?";
private static final String CREATE_ROSTER_ITEM_GROUPS =
"INSERT INTO jiveRosterGroups (rosterID, rank, groupName) VALUES (?, ?, ?)";
private static final String DELETE_ROSTER_ITEM =
"DELETE FROM jiveRoster WHERE rosterID=?";
private static final String LOAD_USERNAMES =
"SELECT DISTINCT username from jiveRoster WHERE jid=?";
private static final String COUNT_ROSTER_ITEMS =
"SELECT COUNT(rosterID) FROM jiveRoster WHERE username=?";
private static final String LOAD_ROSTER =
"SELECT jid, rosterID, sub, ask, recv, nick FROM jiveRoster WHERE username=?";
private static final String LOAD_ROSTER_ITEM_GROUPS =
"SELECT groupName FROM jiveRosterGroups WHERE rosterID=? ORDER BY rank";
private static RosterItemProvider instance = new RosterItemProvider();
public static RosterItemProvider getInstance() {
return instance;
}
/** /**
* <p>Creates a new roster item for the given user (optional operation).</p> * <p>Creates a new roster item for the given user (optional operation).</p>
...@@ -55,12 +82,12 @@ public class DbRosterItemProvider implements RosterItemProvider { ...@@ -55,12 +82,12 @@ public class DbRosterItemProvider implements RosterItemProvider {
* <p>If you don't want roster items edited through messenger, throw UnsupportedOperationException.</p> * <p>If you don't want roster items edited through messenger, throw UnsupportedOperationException.</p>
* *
* @param username the username of the user/chatbot that owns the roster item * @param username the username of the user/chatbot that owns the roster item
* @param item the settings for the roster item to create * @param item the settings for the roster item to create
* @return The created roster item * @return The created roster item
* @throws UnsupportedOperationException If the provider does not support the operation (this is an optional operation)
*/ */
public CachedRosterItem createItem(String username, RosterItem item) public CachedRosterItem createItem(String username, RosterItem item)
throws UserAlreadyExistsException, UnsupportedOperationException { throws UserAlreadyExistsException
{
Connection con = null; Connection con = null;
PreparedStatement pstmt = null; PreparedStatement pstmt = null;
CachedRosterItem cachedItem = null; CachedRosterItem cachedItem = null;
...@@ -93,27 +120,24 @@ public class DbRosterItemProvider implements RosterItemProvider { ...@@ -93,27 +120,24 @@ public class DbRosterItemProvider implements RosterItemProvider {
throw new UserAlreadyExistsException(item.getJid().toBareJID()); throw new UserAlreadyExistsException(item.getJid().toBareJID());
} }
finally { finally {
DbConnectionManager.close(pstmt, con); try { if (pstmt != null) { pstmt.close(); } }
catch (Exception e) { Log.error(e); }
try { if (con != null) { con.close(); } }
catch (Exception e) { Log.error(e); }
} }
return cachedItem; return cachedItem;
} }
private static final String UPDATE_ROSTER_ITEM =
"UPDATE jiveRoster SET sub=?, ask=?, recv=?, nick=? WHERE rosterID=?";
private static final String DELETE_ROSTER_ITEM_GROUPS =
"DELETE FROM jiveRosterGroups WHERE rosterID=?";
/** /**
* <p>Update the roster item in storage with the information contained in the given item (optional operation).</p> * <p>Update the roster item in storage with the information contained in the given item (optional operation).</p>
* <p/> * <p/>
* <p>If you don't want roster items edited through messenger, throw UnsupportedOperationException.</p> * <p>If you don't want roster items edited through messenger, throw UnsupportedOperationException.</p>
* *
* @param username the username of the user/chatbot that owns the roster item * @param username the username of the user/chatbot that owns the roster item
* @param item The roster item to update * @param item The roster item to update
* @throws UserNotFoundException If no entry could be found to update * @throws org.jivesoftware.messenger.user.UserNotFoundException If no entry could be found to update
* @throws UnsupportedOperationException If the provider does not support the operation (this is an optional operation)
*/ */
public void updateItem(String username, CachedRosterItem item) throws UserNotFoundException, UnsupportedOperationException { public void updateItem(String username, CachedRosterItem item) throws UserNotFoundException {
Connection con = null; Connection con = null;
PreparedStatement pstmt = null; PreparedStatement pstmt = null;
long rosterID = item.getID(); long rosterID = item.getID();
...@@ -140,59 +164,22 @@ public class DbRosterItemProvider implements RosterItemProvider { ...@@ -140,59 +164,22 @@ public class DbRosterItemProvider implements RosterItemProvider {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e); Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
} }
finally { finally {
DbConnectionManager.close(pstmt, con); try { if (pstmt != null) { pstmt.close(); } }
catch (Exception e) { Log.error(e); }
try { if (con != null) { con.close(); } }
catch (Exception e) { Log.error(e); }
} }
} }
private static final String CREATE_ROSTER_ITEM_GROUPS =
"INSERT INTO jiveRosterGroups (rosterID, rank, groupName) VALUES (?, ?, ?)";
/**
* <p>Insert the groups into the given roster item.</p>
*
* @param rosterID The roster ID of the item the groups belong to
* @param iter An iterator over the group names to insert
*/
private void insertGroups(long rosterID,
Iterator iter,
PreparedStatement pstmt,
Connection con)
throws SQLException {
try {
pstmt = con.prepareStatement(CREATE_ROSTER_ITEM_GROUPS);
pstmt.setLong(1, rosterID);
for (int i = 0; iter.hasNext(); i++) {
pstmt.setInt(2, i);
pstmt.setString(3, (String)iter.next());
pstmt.executeUpdate();
}
}
finally {
try {
if (pstmt != null) {
pstmt.close();
}
}
catch (Exception e) {
Log.error(e);
}
}
}
private static final String DELETE_ROSTER_ITEM =
"DELETE FROM jiveRoster WHERE rosterID=?";
/** /**
* <p>Delete the roster item with the given itemJID for the user (optional operation).</p> * <p>Delete the roster item with the given itemJID for the user (optional operation).</p>
* <p/> * <p/>
* <p>If you don't want roster items deleted through messenger, throw UnsupportedOperationException.</p> * <p>If you don't want roster items deleted through messenger, throw UnsupportedOperationException.</p>
* *
* @param username the long ID of the user/chatbot that owns the roster item * @param username the long ID of the user/chatbot that owns the roster item
* @param rosterItemID The roster item to delete * @param rosterItemID The roster item to delete
* @throws UnsupportedOperationException If the provider does not support the operation (this is an optional operation)
*/ */
public void deleteItem(String username, long rosterItemID) public void deleteItem(String username, long rosterItemID) {
throws UnsupportedOperationException {
// Only try to remove the user if they exist in the roster already: // Only try to remove the user if they exist in the roster already:
Connection con = null; Connection con = null;
PreparedStatement pstmt = null; PreparedStatement pstmt = null;
...@@ -214,12 +201,13 @@ public class DbRosterItemProvider implements RosterItemProvider { ...@@ -214,12 +201,13 @@ public class DbRosterItemProvider implements RosterItemProvider {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e); Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
} }
finally { finally {
DbConnectionManager.close(pstmt, con); try { if (pstmt != null) { pstmt.close(); } }
catch (Exception e) { Log.error(e); }
try { if (con != null) { con.close(); } }
catch (Exception e) { Log.error(e); }
} }
} }
private static final String LOAD_USERNAMES = "SELECT DISTINCT username from jiveRoster WHERE jid=?";
/** /**
* Returns an iterator on the usernames whose roster includes the specified JID. * Returns an iterator on the usernames whose roster includes the specified JID.
* *
...@@ -243,14 +231,14 @@ public class DbRosterItemProvider implements RosterItemProvider { ...@@ -243,14 +231,14 @@ public class DbRosterItemProvider implements RosterItemProvider {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e); Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
} }
finally { finally {
DbConnectionManager.close(pstmt, con); try { if (pstmt != null) { pstmt.close(); } }
catch (Exception e) { Log.error(e); }
try { if (con != null) { con.close(); } }
catch (Exception e) { Log.error(e); }
} }
return answer.iterator(); return answer.iterator();
} }
private static final String COUNT_ROSTER_ITEMS =
"SELECT COUNT(rosterID) FROM jiveRoster WHERE username=?";
/** /**
* <p>Obtain a count of the number of roster items available for the given user.</p> * <p>Obtain a count of the number of roster items available for the given user.</p>
* *
...@@ -274,14 +262,14 @@ public class DbRosterItemProvider implements RosterItemProvider { ...@@ -274,14 +262,14 @@ public class DbRosterItemProvider implements RosterItemProvider {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e); Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
} }
finally { finally {
DbConnectionManager.close(pstmt, con); try { if (pstmt != null) { pstmt.close(); } }
catch (Exception e) { Log.error(e); }
try { if (con != null) { con.close(); } }
catch (Exception e) { Log.error(e); }
} }
return count; return count;
} }
private static final String LOAD_ROSTER = "SELECT jid, rosterID, sub, ask, recv, nick FROM jiveRoster WHERE username=?";
private static final String LOAD_ROSTER_ITEM_GROUPS = "SELECT groupName FROM jiveRosterGroups WHERE rosterID=? ORDER BY rank";
/** /**
* <p>Retrieve an iterator of RosterItems for the given user.</p> * <p>Retrieve an iterator of RosterItems for the given user.</p>
* <p/> * <p/>
...@@ -301,6 +289,8 @@ public class DbRosterItemProvider implements RosterItemProvider { ...@@ -301,6 +289,8 @@ public class DbRosterItemProvider implements RosterItemProvider {
pstmt = con.prepareStatement(LOAD_ROSTER); pstmt = con.prepareStatement(LOAD_ROSTER);
pstmt.setString(1, username); pstmt.setString(1, username);
ResultSet rs = pstmt.executeQuery(); ResultSet rs = pstmt.executeQuery();
// TODO: this code must be refactored ASAP. Not legal to have two open pstmts
// TODO: on many databases.
while (rs.next()) { while (rs.next()) {
CachedRosterItem item = new CachedRosterItemImpl(rs.getLong(2), CachedRosterItem item = new CachedRosterItemImpl(rs.getLong(2),
new JID(rs.getString(1)), new JID(rs.getString(1)),
...@@ -321,13 +311,43 @@ public class DbRosterItemProvider implements RosterItemProvider { ...@@ -321,13 +311,43 @@ public class DbRosterItemProvider implements RosterItemProvider {
itemList.add(item); itemList.add(item);
} }
finally { finally {
DbConnectionManager.close(gstmt, con); try { if (pstmt != null) { pstmt.close(); } }
catch (Exception e) { Log.error(e); }
try { if (con != null) { con.close(); } }
catch (Exception e) { Log.error(e); }
} }
} }
} }
catch (SQLException e) { catch (SQLException e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e); Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
} }
finally {
try { if (pstmt != null) { pstmt.close(); } }
catch (Exception e) { Log.error(e); }
try { if (con != null) { con.close(); } }
catch (Exception e) { Log.error(e); }
}
return itemList.iterator();
}
/**
* <p>Insert the groups into the given roster item.</p>
*
* @param rosterID The roster ID of the item the groups belong to
* @param iter An iterator over the group names to insert
*/
private void insertGroups(long rosterID, Iterator iter, PreparedStatement pstmt,
Connection con) throws SQLException
{
try {
pstmt = con.prepareStatement(CREATE_ROSTER_ITEM_GROUPS);
pstmt.setLong(1, rosterID);
for (int i = 0; iter.hasNext(); i++) {
pstmt.setInt(2, i);
pstmt.setString(3, (String)iter.next());
pstmt.executeUpdate();
}
}
finally { finally {
try { try {
if (pstmt != null) { if (pstmt != null) {
...@@ -337,15 +357,6 @@ public class DbRosterItemProvider implements RosterItemProvider { ...@@ -337,15 +357,6 @@ public class DbRosterItemProvider implements RosterItemProvider {
catch (Exception e) { catch (Exception e) {
Log.error(e); Log.error(e);
} }
try {
if (con != null) {
con.close();
}
}
catch (Exception e) {
Log.error(e);
}
} }
return itemList.iterator();
} }
} }
\ No newline at end of file
...@@ -9,25 +9,45 @@ ...@@ -9,25 +9,45 @@
* a copy of which is included in this distribution. * a copy of which is included in this distribution.
*/ */
package org.jivesoftware.messenger.user.spi; package org.jivesoftware.messenger.roster;
import org.xmpp.packet.JID;
import org.jivesoftware.util.Cache; import org.jivesoftware.util.Cache;
import org.jivesoftware.util.CacheManager; import org.jivesoftware.util.CacheManager;
import org.jivesoftware.messenger.container.BasicModule; import org.jivesoftware.messenger.container.BasicModule;
import org.jivesoftware.messenger.user.*; import org.jivesoftware.messenger.roster.spi.CachedRosterImpl;
import org.jivesoftware.messenger.user.UserNotFoundException;
import org.jivesoftware.messenger.auth.UnauthorizedException; import org.jivesoftware.messenger.auth.UnauthorizedException;
import org.xmpp.packet.JID;
import java.util.Iterator; import java.util.Iterator;
public class RosterManagerImpl extends BasicModule implements RosterManager { /**
* A simple service that allows components to retrieve a roster based solely on the ID
* of the owner. Users have convenience methods for obtaining a roster associated with
* the owner. However there are many components that need to retrieve the roster
* based solely on the generic ID owner key. This interface defines a service that can
* do that. This allows classes that generically manage resource for resource owners
* (such as presence updates) to generically offer their services without knowing or
* caring if the roster owner is a user, chatbot, etc.
*
* @author Iain Shigeoka
*/
public class RosterManager extends BasicModule {
private Cache rosterCache = null; private Cache rosterCache = null;
public RosterManagerImpl() { public RosterManager() {
super("Roster Manager"); super("Roster Manager");
} }
/**
* Returns the roster for the given username.
*
* @param username the username to search for.
* @return the roster associated with the ID.
* @throws org.jivesoftware.messenger.user.UserNotFoundException if the ID does not correspond to a known
* entity on the server.
*/
public CachedRoster getRoster(String username) throws UserNotFoundException { public CachedRoster getRoster(String username) throws UserNotFoundException {
if (rosterCache == null) { if (rosterCache == null) {
rosterCache = CacheManager.getCache("username2roster"); rosterCache = CacheManager.getCache("username2roster");
...@@ -47,6 +67,12 @@ public class RosterManagerImpl extends BasicModule implements RosterManager { ...@@ -47,6 +67,12 @@ public class RosterManagerImpl extends BasicModule implements RosterManager {
return roster; return roster;
} }
/**
* Removes the entire roster of a given user. This is necessary when a user
* account is being deleted from the server.
*
* @param user the user.
*/
public void deleteRoster(JID user) { public void deleteRoster(JID user) {
try { try {
String username = user.getNode(); String username = user.getNode();
...@@ -65,7 +91,7 @@ public class RosterManagerImpl extends BasicModule implements RosterManager { ...@@ -65,7 +91,7 @@ public class RosterManagerImpl extends BasicModule implements RosterManager {
CacheManager.getCache("username2roster").remove(username); CacheManager.getCache("username2roster").remove(username);
// Get the rosters that have a reference to the deleted user // Get the rosters that have a reference to the deleted user
RosterItemProvider rosteItemProvider = UserProviderFactory.getRosterItemProvider(); RosterItemProvider rosteItemProvider = RosterItemProvider.getInstance();
Iterator<String> usernames = rosteItemProvider.getUsernames(user.toBareJID()); Iterator<String> usernames = rosteItemProvider.getUsernames(user.toBareJID());
while (usernames.hasNext()) { while (usernames.hasNext()) {
username = usernames.next(); username = usernames.next();
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
* a copy of which is included in this distribution. * a copy of which is included in this distribution.
*/ */
package org.jivesoftware.messenger.user.spi; package org.jivesoftware.messenger.roster.spi;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
...@@ -22,15 +22,14 @@ import org.jivesoftware.messenger.SessionManager; ...@@ -22,15 +22,14 @@ import org.jivesoftware.messenger.SessionManager;
import org.jivesoftware.messenger.XMPPServer; import org.jivesoftware.messenger.XMPPServer;
import org.jivesoftware.messenger.spi.BasicServer; import org.jivesoftware.messenger.spi.BasicServer;
import org.jivesoftware.messenger.auth.UnauthorizedException; import org.jivesoftware.messenger.auth.UnauthorizedException;
import org.jivesoftware.messenger.user.BasicRoster; import org.jivesoftware.messenger.roster.BasicRoster;
import org.jivesoftware.messenger.user.BasicRosterItem; import org.jivesoftware.messenger.roster.BasicRosterItem;
import org.jivesoftware.messenger.user.CachedRoster; import org.jivesoftware.messenger.roster.CachedRoster;
import org.jivesoftware.messenger.user.CachedRosterItem; import org.jivesoftware.messenger.roster.RosterItem;
import org.jivesoftware.messenger.user.RosterItem; import org.jivesoftware.messenger.roster.CachedRosterItem;
import org.jivesoftware.messenger.user.RosterItemProvider; import org.jivesoftware.messenger.roster.*;
import org.jivesoftware.messenger.user.UserAlreadyExistsException; import org.jivesoftware.messenger.user.UserAlreadyExistsException;
import org.jivesoftware.messenger.user.UserNotFoundException; import org.jivesoftware.messenger.user.UserNotFoundException;
import org.jivesoftware.messenger.user.UserProviderFactory;
import org.jivesoftware.util.CacheSizes; import org.jivesoftware.util.CacheSizes;
import org.jivesoftware.util.LocaleUtils; import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log; import org.jivesoftware.util.Log;
...@@ -68,7 +67,7 @@ public class CachedRosterImpl extends BasicRoster implements CachedRoster { ...@@ -68,7 +67,7 @@ public class CachedRosterImpl extends BasicRoster implements CachedRoster {
sessionManager = SessionManager.getInstance(); sessionManager = SessionManager.getInstance();
this.username = username; this.username = username;
rosterItemProvider = UserProviderFactory.getRosterItemProvider(); rosterItemProvider = RosterItemProvider.getInstance();
Iterator items = rosterItemProvider.getItems(username); Iterator items = rosterItemProvider.getItems(username);
while (items.hasNext()) { while (items.hasNext()) {
RosterItem item = (RosterItem)items.next(); RosterItem item = (RosterItem)items.next();
......
...@@ -9,14 +9,16 @@ ...@@ -9,14 +9,16 @@
* a copy of which is included in this distribution. * a copy of which is included in this distribution.
*/ */
package org.jivesoftware.messenger.user.spi; package org.jivesoftware.messenger.roster.spi;
import java.util.List; import java.util.List;
import java.util.LinkedList; import java.util.LinkedList;
import org.jivesoftware.messenger.user.BasicRosterItem; import org.jivesoftware.messenger.roster.BasicRosterItem;
import org.jivesoftware.messenger.user.CachedRosterItem; import org.jivesoftware.messenger.roster.CachedRosterItem;
import org.jivesoftware.messenger.user.RosterItem; import org.jivesoftware.messenger.roster.RosterItem;
import org.jivesoftware.messenger.roster.RosterItem;
import org.jivesoftware.messenger.roster.CachedRosterItem;
import org.jivesoftware.util.CacheSizes; import org.jivesoftware.util.CacheSizes;
import org.xmpp.packet.JID; import org.xmpp.packet.JID;
......
...@@ -15,6 +15,7 @@ import org.dom4j.Document; ...@@ -15,6 +15,7 @@ import org.dom4j.Document;
import org.dom4j.io.SAXReader; import org.dom4j.io.SAXReader;
import org.jivesoftware.database.DbConnectionManager; import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.messenger.*; import org.jivesoftware.messenger.*;
import org.jivesoftware.messenger.roster.RosterManager;
import org.jivesoftware.messenger.audit.AuditManager; import org.jivesoftware.messenger.audit.AuditManager;
import org.jivesoftware.messenger.audit.spi.AuditManagerImpl; import org.jivesoftware.messenger.audit.spi.AuditManagerImpl;
import org.jivesoftware.messenger.container.Module; import org.jivesoftware.messenger.container.Module;
...@@ -27,10 +28,8 @@ import org.jivesoftware.messenger.handler.*; ...@@ -27,10 +28,8 @@ import org.jivesoftware.messenger.handler.*;
import org.jivesoftware.messenger.muc.spi.MultiUserChatServerImpl; import org.jivesoftware.messenger.muc.spi.MultiUserChatServerImpl;
import org.jivesoftware.messenger.muc.MultiUserChatServer; import org.jivesoftware.messenger.muc.MultiUserChatServer;
import org.jivesoftware.messenger.transport.TransportHandler; import org.jivesoftware.messenger.transport.TransportHandler;
import org.jivesoftware.messenger.user.RosterManager; import org.jivesoftware.messenger.roster.RosterManager;
import org.jivesoftware.messenger.user.UserManager; import org.jivesoftware.messenger.user.UserManager;
import org.jivesoftware.messenger.user.spi.RosterManagerImpl;
import org.jivesoftware.messenger.user.spi.UserManagerImpl;
import org.jivesoftware.util.LocaleUtils; import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log; import org.jivesoftware.util.Log;
import org.jivesoftware.util.Version; import org.jivesoftware.util.Version;
...@@ -193,8 +192,7 @@ public class BasicServer implements XMPPServer { ...@@ -193,8 +192,7 @@ public class BasicServer implements XMPPServer {
// Load boot modules // Load boot modules
loadModule(RoutingTableImpl.class.getName()); loadModule(RoutingTableImpl.class.getName());
loadModule(AuditManagerImpl.class.getName()); loadModule(AuditManagerImpl.class.getName());
loadModule(UserManagerImpl.class.getName()); loadModule(RosterManager.class.getName());
loadModule(RosterManagerImpl.class.getName());
loadModule(PrivateStorage.class.getName()); loadModule(PrivateStorage.class.getName());
// Load core modules // Load core modules
loadModule(ConnectionManagerImpl.class.getName()); loadModule(ConnectionManagerImpl.class.getName());
...@@ -541,7 +539,7 @@ public class BasicServer implements XMPPServer { ...@@ -541,7 +539,7 @@ public class BasicServer implements XMPPServer {
} }
public RosterManager getRosterManager() { public RosterManager getRosterManager() {
return (RosterManager) modules.get(RosterManagerImpl.class); return (RosterManager) modules.get(RosterManager.class);
} }
public PresenceManager getPresenceManager() { public PresenceManager getPresenceManager() {
...@@ -603,7 +601,7 @@ public class BasicServer implements XMPPServer { ...@@ -603,7 +601,7 @@ public class BasicServer implements XMPPServer {
} }
public UserManager getUserManager() { public UserManager getUserManager() {
return (UserManager) modules.get(UserManagerImpl.class); return UserManager.getInstance();
} }
public AuditManager getAuditManager() { public AuditManager getAuditManager() {
......
...@@ -9,53 +9,32 @@ ...@@ -9,53 +9,32 @@
* a copy of which is included in this distribution. * a copy of which is included in this distribution.
*/ */
package org.jivesoftware.messenger.user.spi; package org.jivesoftware.messenger.user;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import org.jivesoftware.database.DbConnectionManager; import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.messenger.auth.UnauthorizedException;
import org.jivesoftware.messenger.container.BasicModule;
import org.jivesoftware.messenger.user.User;
import org.jivesoftware.messenger.user.UserAlreadyExistsException;
import org.jivesoftware.messenger.user.UserManager;
import org.jivesoftware.messenger.user.UserNotFoundException;
import org.jivesoftware.messenger.user.UserProviderFactory;
import org.jivesoftware.messenger.XMPPServer;
import org.jivesoftware.stringprep.Stringprep;
import org.jivesoftware.stringprep.StringprepException;
import org.jivesoftware.util.Cache;
import org.jivesoftware.util.CacheManager;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log; import org.jivesoftware.util.Log;
import org.jivesoftware.util.StringUtils; import org.jivesoftware.util.StringUtils;
import org.jivesoftware.util.LocaleUtils;
import java.sql.*;
import java.util.*;
import java.util.Date;
/** /**
* Database implementation of the UserManager interface. * Default implementation of the UserProvider interface, which reads and writes data
* It uses the DbUser class along with the jiveUser database * from the <tt>jiveUser</tt> database table.
* table to store and manipulate user information.<p>
* <p/>
* This UserManager implementation uses two caches to vastly improve speed:
* <ul>
* <li> id2userCache
* <li> name2idCache
* </ul><p>
* <p/>
* If making your own UserManager implementation, it's
* highly recommended that you also use these caches.
* *
* @author Iain Shigeoka * @author Matt Tucker
*/ */
public class UserManagerImpl extends BasicModule implements UserManager { public class DefaultUserProvider implements UserProvider {
private static final String USER_COUNT = "SELECT count(*) FROM jiveUser"; private static final String LOAD_USER =
private static final String ALL_USERS = "SELECT username from jiveUser"; "SELECT name, email, creationDate, modificationDate FROM jiveUser WHERE username=?";
private static final String USER_COUNT =
"SELECT count(*) FROM jiveUser";
private static final String ALL_USERS =
"SELECT username FROM jiveUser";
private static final String INSERT_USER = private static final String INSERT_USER =
"INSERT INTO jiveUser (username,password,name,email,creationDate,modificationDate) " + "INSERT INTO jiveUser (username,password,name,email,creationDate,modificationDate) " +
"VALUES (?,?,?,?,?,?)"; "VALUES (?,?,?,?,?,?)";
...@@ -67,102 +46,95 @@ public class UserManagerImpl extends BasicModule implements UserManager { ...@@ -67,102 +46,95 @@ public class UserManagerImpl extends BasicModule implements UserManager {
"DELETE FROM jiveVCard WHERE username=?"; "DELETE FROM jiveVCard WHERE username=?";
private static final String DELETE_USER = private static final String DELETE_USER =
"DELETE FROM jiveUser WHERE username=?"; "DELETE FROM jiveUser WHERE username=?";
private static final String UPDATE_NAME =
"UPDATE jiveUser SET name=? WHERE username=?";
private static final String UPDATE_EMAIL =
"UPDATE jiveUser SET email=? WHERE username=?";
private static final String UPDATE_CREATION_DATE =
"UPDATE jiveUser SET creationDate=? WHERE username=?";
private static final String UPDATE_MODIFICATION_DATE =
"UPDATE jiveUser SET modificationDate=? WHERE username=?";
private static final String UPDATE_PASSWORD =
"UPDATE jiveUser SET password=? WHERE username=?";
private Cache userCache; public User loadUser(String username) throws UserNotFoundException {
Connection con = null;
public UserManagerImpl() { PreparedStatement pstmt = null;
super("User Manager");
}
private void initializeCaches() {
CacheManager.initializeCache("userCache", 512 * 1024);
CacheManager.initializeCache("username2roster", 512 * 1024);
userCache = CacheManager.getCache("userCache");
}
public User createUser(String username, String password, String email) throws UserAlreadyExistsException, UnauthorizedException {
User newUser = null;
// Strip extra or invisible characters from the username so that existing
// usernames can't be forged.
try { try {
username = Stringprep.nameprep(username, true); con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(LOAD_USER);
pstmt.setString(1, username);
ResultSet rs = pstmt.executeQuery();
if (!rs.next()) {
throw new UserNotFoundException();
}
String name = rs.getString(1);
String email = rs.getString(2);
Date creationDate = new Date(Long.parseLong(rs.getString(3).trim()));
Date modificationDate = new Date(Long.parseLong(rs.getString(4).trim()));
rs.close();
return new User(username, name, email, creationDate, modificationDate);
} }
catch (StringprepException e) { catch (Exception e) {
Log.error(e); throw new UserNotFoundException(e);
} }
username = StringUtils.replace(username, "&nbsp;", ""); finally {
try { try { if (pstmt != null) { pstmt.close(); } }
getUser(username); catch (Exception e) { Log.error(e); }
try { if (con != null) { con.close(); } }
catch (Exception e) { Log.error(e); }
}
}
public User createUser(String username, String password, String name, String email)
throws UserAlreadyExistsException
{
try {
loadUser(username);
// The user already exists since no exception, so: // The user already exists since no exception, so:
throw new UserAlreadyExistsException(); throw new UserAlreadyExistsException("Username " + username + " already exists");
} }
catch (UserNotFoundException unfe) { catch (UserNotFoundException unfe) {
// The user doesn't already exist so we can create a new user // The user doesn't already exist so we can create a new user
Date now = new Date();
Connection con = null;
PreparedStatement pstmt = null;
try { try {
if (email == null || email.length() == 0) { con = DbConnectionManager.getConnection();
email = " "; pstmt = con.prepareStatement(INSERT_USER);
pstmt.setString(1, username);
pstmt.setString(2, password);
if (name == null) {
pstmt.setNull(3, Types.VARCHAR);
} }
Connection con = null; else {
PreparedStatement pstmt = null; pstmt.setString(3, name);
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(INSERT_USER);
pstmt.setString(1, username);
pstmt.setString(2, password);
pstmt.setString(3, "");
pstmt.setString(4, email);
Date now = new Date();
pstmt.setString(5, StringUtils.dateToMillis(now));
pstmt.setString(6, StringUtils.dateToMillis(now));
pstmt.execute();
} }
catch (Exception e) { if (email == null) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e); pstmt.setNull(4, Types.VARCHAR);
} }
finally { else {
DbConnectionManager.close(pstmt, con); pstmt.setString(4, email);
} }
newUser = getUser(username); pstmt.setString(5, StringUtils.dateToMillis(now));
pstmt.setString(6, StringUtils.dateToMillis(now));
pstmt.execute();
} }
catch (UserNotFoundException e) { catch (Exception e) {
throw new UnauthorizedException("Created an account but could not load it. username: " Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
+ username + " pass: " + password + " email: " + email, e);
} }
finally {
try { if (pstmt != null) { pstmt.close(); } }
catch (Exception e) { Log.error(e); }
try { if (con != null) { con.close(); } }
catch (Exception e) { Log.error(e); }
}
return new User(username, name, email, now, now);
} }
return newUser;
}
public User getUser(String username) throws UserNotFoundException {
if (username == null) {
throw new UserNotFoundException("Username with null value is not valid.");
}
try {
username = Stringprep.nameprep(username, false);
}
catch (StringprepException e) {
e.printStackTrace();
}
User user = (User)userCache.get(username);
if (user == null) {
// Hack to make sure user exists.
UserProviderFactory.getUserInfoProvider().getInfo(username);
user = loadUser(username);
}
if (user == null) {
throw new UserNotFoundException();
}
return user;
}
private User loadUser(String username) {
User user = new UserImpl(username);
userCache.put(username, user);
return user;
} }
public void deleteUser(User user) throws UnauthorizedException { public void deleteUser(String username) {
String username = user.getUsername();
Connection con = null; Connection con = null;
PreparedStatement pstmt = null; PreparedStatement pstmt = null;
boolean abortTransaction = false; boolean abortTransaction = false;
...@@ -193,25 +165,16 @@ public class UserManagerImpl extends BasicModule implements UserManager { ...@@ -193,25 +165,16 @@ public class UserManagerImpl extends BasicModule implements UserManager {
abortTransaction = true; abortTransaction = true;
} }
finally { finally {
try { try { if (pstmt != null) { pstmt.close(); } }
if (pstmt != null) { catch (Exception e) { Log.error(e); }
pstmt.close();
}
}
catch (Exception e) {
Log.error(e);
}
DbConnectionManager.closeTransactionConnection(con, abortTransaction); DbConnectionManager.closeTransactionConnection(con, abortTransaction);
} }
// Expire user cache.
userCache.remove(user.getUsername());
} }
public int getUserCount() { public int getUserCount() {
int count = 0; int count = 0;
Connection con = null; Connection con = null;
PreparedStatement pstmt = null; PreparedStatement pstmt = null;
try { try {
con = DbConnectionManager.getConnection(); con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(USER_COUNT); pstmt = con.prepareStatement(USER_COUNT);
...@@ -224,13 +187,16 @@ public class UserManagerImpl extends BasicModule implements UserManager { ...@@ -224,13 +187,16 @@ public class UserManagerImpl extends BasicModule implements UserManager {
Log.error(e); Log.error(e);
} }
finally { finally {
DbConnectionManager.close(pstmt, con); try { if (pstmt != null) { pstmt.close(); } }
catch (Exception e) { Log.error(e); }
try { if (con != null) { con.close(); } }
catch (Exception e) { Log.error(e); }
} }
return count; return count;
} }
public Iterator users() throws UnauthorizedException { public Collection<User> getUsers() {
List<String> users = new ArrayList<String>(500); List<String> usernames = new ArrayList<String>(500);
Connection con = null; Connection con = null;
PreparedStatement pstmt = null; PreparedStatement pstmt = null;
try { try {
...@@ -241,7 +207,7 @@ public class UserManagerImpl extends BasicModule implements UserManager { ...@@ -241,7 +207,7 @@ public class UserManagerImpl extends BasicModule implements UserManager {
// to load the entire result set into memory. // to load the entire result set into memory.
DbConnectionManager.setFetchSize(rs, 500); DbConnectionManager.setFetchSize(rs, 500);
while (rs.next()) { while (rs.next()) {
users.add(rs.getString(1)); usernames.add(rs.getString(1));
} }
rs.close(); rs.close();
} }
...@@ -249,13 +215,16 @@ public class UserManagerImpl extends BasicModule implements UserManager { ...@@ -249,13 +215,16 @@ public class UserManagerImpl extends BasicModule implements UserManager {
Log.error(e); Log.error(e);
} }
finally { finally {
DbConnectionManager.close(pstmt, con); try { if (pstmt != null) { pstmt.close(); } }
catch (Exception e) { Log.error(e); }
try { if (con != null) { con.close(); } }
catch (Exception e) { Log.error(e); }
} }
return new UserIterator((String[])users.toArray(new String[users.size()])); return new UserCollection((String[])usernames.toArray(new String[usernames.size()]));
} }
public Iterator users(int startIndex, int numResults) throws UnauthorizedException { public Collection<User> getUsers(int startIndex, int numResults) {
List<String> users = new ArrayList<String>(); List<String> usernames = new ArrayList<String>(numResults);
Connection con = null; Connection con = null;
PreparedStatement pstmt = null; PreparedStatement pstmt = null;
try { try {
...@@ -270,7 +239,7 @@ public class UserManagerImpl extends BasicModule implements UserManager { ...@@ -270,7 +239,7 @@ public class UserManagerImpl extends BasicModule implements UserManager {
// Now read in desired number of results (or stop if we run out of results). // Now read in desired number of results (or stop if we run out of results).
for (int i = 0; i < numResults; i++) { for (int i = 0; i < numResults; i++) {
if (rs.next()) { if (rs.next()) {
users.add(rs.getString(1)); usernames.add(rs.getString(1));
} }
else { else {
break; break;
...@@ -282,17 +251,117 @@ public class UserManagerImpl extends BasicModule implements UserManager { ...@@ -282,17 +251,117 @@ public class UserManagerImpl extends BasicModule implements UserManager {
Log.error(e); Log.error(e);
} }
finally { finally {
DbConnectionManager.close(pstmt, con); try { if (pstmt != null) { pstmt.close(); } }
catch (Exception e) { Log.error(e); }
try { if (con != null) { con.close(); } }
catch (Exception e) { Log.error(e); }
} }
return new UserIterator((String[])users.toArray(new String[users.size()])); return new UserCollection((String[])usernames.toArray(new String[usernames.size()]));
} }
// ##################################################################### public void setName(String username, String name) throws UserNotFoundException {
// Module management Connection con = null;
// ##################################################################### PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(UPDATE_NAME);
pstmt.setString(1, name);
pstmt.setString(2, username);
pstmt.executeUpdate();
}
catch (SQLException sqle) {
throw new UserNotFoundException(sqle);
}
finally {
try { if (pstmt != null) pstmt.close(); }
catch (Exception e) { Log.error(e); }
try { if (con != null) con.close(); }
catch (Exception e) { Log.error(e); }
}
}
public void setEmail(String username, String email) throws UserNotFoundException {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(UPDATE_EMAIL);
pstmt.setString(1, email);
pstmt.setString(2, username);
pstmt.executeUpdate();
}
catch (SQLException sqle) {
throw new UserNotFoundException(sqle);
}
finally {
try { if (pstmt != null) pstmt.close(); }
catch (Exception e) { Log.error(e); }
try { if (con != null) con.close(); }
catch (Exception e) { Log.error(e); }
}
}
public void initialize(XMPPServer server) { public void setCreationDate(String username, Date creationDate) throws UserNotFoundException {
super.initialize(server); Connection con = null;
initializeCaches(); PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(UPDATE_CREATION_DATE);
pstmt.setString(1, StringUtils.dateToMillis(creationDate));
pstmt.setString(2, username);
pstmt.executeUpdate();
}
catch (SQLException sqle) {
throw new UserNotFoundException(sqle);
}
finally {
try { if (pstmt != null) pstmt.close(); }
catch (Exception e) { Log.error(e); }
try { if (con != null) con.close(); }
catch (Exception e) { Log.error(e); }
}
}
public void setModificationDate(String username, Date modificationDate) throws UserNotFoundException {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(UPDATE_MODIFICATION_DATE);
pstmt.setString(1, StringUtils.dateToMillis(modificationDate));
pstmt.setString(2, username);
pstmt.executeUpdate();
}
catch (SQLException sqle) {
throw new UserNotFoundException(sqle);
}
finally {
try { if (pstmt != null) pstmt.close(); }
catch (Exception e) { Log.error(e); }
try { if (con != null) con.close(); }
catch (Exception e) { Log.error(e); }
}
}
public void setPassword(String username, String password) throws UserNotFoundException
{
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(UPDATE_PASSWORD);
pstmt.setString(1, password);
pstmt.setString(2, username);
pstmt.executeUpdate();
}
catch (SQLException sqle) {
throw new UserNotFoundException(sqle);
}
finally {
try { if (pstmt != null) pstmt.close(); }
catch (Exception e) { Log.error(e); }
try { if (con != null) con.close(); }
catch (Exception e) { Log.error(e); }
}
} }
} }
\ No newline at end of file
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.messenger.user;
import java.util.Iterator;
/**
* <p>Defines the provider methods required for creating, reading, updating and deleting roster items.</p>
* <p/>
* <p>Rosters are another user resource accessed via the user or chatbot's long ID. A user/chatbot may have
* zero or more roster items and each roster item may have zero or more groups. Each roster item is
* additionaly keyed on a XMPP jid. In most cases, the entire roster will be read in from memory and manipulated
* or sent to the user. However some operations will need to retrive specific roster items rather than the
* entire roster.</p>
*
* @author Iain Shigeoka
*/
public interface RosterItemProvider {
/**
* <p>Creates a new roster item for the given user (optional operation).</p>
* <p/>
* <p><b>Important!</b> The item passed as a parameter to this method is strictly a convenience for passing all
* of the data needed for a new roster item. The roster item returned from the method will be cached by Messenger.
* In some cases, the roster item passed in will be passed back out. However, if an implementation may
* return RosterItems as a separate class (for example, a RosterItem that directly accesses the backend
* storage, or one that is an object in an object database).
* <p/>
* <p>If you don't want roster items edited through messenger, throw UnsupportedOperationException.</p>
*
* @param username the username of the user/chatbot that owns the roster item
* @param item the settings for the roster item to create
* @return The created roster item
* @throws UnsupportedOperationException If the provider does not support the operation (this is an optional operation)
*/
CachedRosterItem createItem(String username, RosterItem item) throws UserAlreadyExistsException, UnsupportedOperationException;
/**
* <p>Update the roster item in storage with the information contained in the given item (optional operation).</p>
* <p/>
* <p>If you don't want roster items edited through messenger, throw UnsupportedOperationException.</p>
*
* @param username the username of the user/chatbot that owns the roster item
* @param item The roster item to update
* @throws UserNotFoundException If no entry could be found to update
* @throws UnsupportedOperationException If the provider does not support the operation (this is an optional operation)
*/
void updateItem(String username, CachedRosterItem item) throws UserNotFoundException, UnsupportedOperationException;
/**
* <p>Delete the roster item with the given itemJID for the user (optional operation).</p>
* <p/>
* <p>If you don't want roster items deleted through messenger, throw UnsupportedOperationException.</p>
*
* @param username the long ID of the user/chatbot that owns the roster item
* @param rosterItemID The roster item to delete
* @throws UnsupportedOperationException If the provider does not support the operation (this is an optional operation)
*/
void deleteItem(String username, long rosterItemID) throws UnsupportedOperationException;
/**
* Returns an iterator on the usernames whose roster includes the specified JID.
*
* @param jid the jid that the rosters should include.
* @return an iterator on the usernames whose roster includes the specified JID.
*/
Iterator<String> getUsernames(String jid);
/**
* <p>Obtain a count of the number of roster items available for the given user.</p>
*
* @param username the username of the user/chatbot that owns the roster items
* @return The number of roster items available for the user
*/
int getItemCount(String username);
/**
* <p>Retrieve an iterator of RosterItems for the given user.</p>
* <p/>
* <p>This method will commonly be called when a user logs in. The data will be cached
* in memory when possible. However, some rosters may be very large so items may need
* to be retrieved from the provider more frequently than usual for provider data.
*
* @param username the username of the user/chatbot that owns the roster items
* @return An iterator of all RosterItems owned by the user
*/
Iterator getItems(String username);
}
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.messenger.user;
import org.xmpp.packet.JID;
/**
* <p>A simple service that allows components to retrieve a roster based solely on the ID of the owner.</p>
* <p/>
* <p>The User, Chatbot, and other ID based 'resources owners' have convenience methods for obtaining
* a roster associated with the owner. However there are many components that need to retrieve the
* roster based solely on the generic ID owner key. This interface defines a service that can do that.
* This allows classes that generically manage resource for resource owners (such as presence updates)
* to generically offer their services without knowing or caring if the roster owner is a user, chatbot, etc.</p>
*
* @author Iain Shigeoka
*/
public interface RosterManager {
/**
* <p>Obtain the roster for the given username.</p>
*
* @param username the username to search for
* @return The roster associated with the ID
* @throws UserNotFoundException If the ID does not correspond to a known entity on the server
*/
CachedRoster getRoster(String username) throws UserNotFoundException;
/**
* Removes the entire roster of a given user. This is necessary when a user account is being
* deleted from the server.
*
* @param user the user to remove his roster.
*/
void deleteRoster(JID user);
}
...@@ -11,53 +11,150 @@ ...@@ -11,53 +11,150 @@
package org.jivesoftware.messenger.user; package org.jivesoftware.messenger.user;
import org.jivesoftware.messenger.auth.AuthToken; import org.jivesoftware.messenger.spi.BasicServer;
import org.jivesoftware.messenger.auth.Permissions; import org.jivesoftware.messenger.roster.CachedRoster;
import org.jivesoftware.messenger.auth.UnauthorizedException; import org.jivesoftware.util.Log;
import java.util.Iterator; import org.jivesoftware.util.Cacheable;
import org.jivesoftware.util.CacheSizes;
import org.jivesoftware.database.DbConnectionManager;
import java.util.*;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/** /**
* <p>The User interface provides information about and services for users of the system. Users can be * Encapsulates information about a user. New users are created using
* identified by a unique id or username. Users can also be organized into groups for easier * {@link UserManager#createUser(String, String, String, String)}. All user
* management of permissions.</p> * properties are loaded on demand and are read from the <tt>jiveUserProp</tt>
* <p/> * database table. The currently-installed {@link UserProvider} is used for
* <p>Security for User objects is provide by UserProxy protection proxy objects.</p> * setting all other user data and some operations may not be supported
* depending on the capabilities of the {@link UserProvider}.
* *
* @author Iain Shigeoka * @author Matt Tucker
*/ */
public interface User { public class User implements Cacheable {
/** private static final String LOAD_PROPERTIES =
* <p>Returns the entity's username.</p> "SELECT name, propValue FROM jiveUserProp WHERE username=?";
* <p/> private static final String DELETE_PROPERTY =
* <p>All usernames must be unique in the system.</p> "DELETE FROM jiveUserProp WHERE username=? AND name=?";
* private static final String UPDATE_PROPERTY =
* @return the username of the entity. "UPDATE jiveUserProp SET propValue=? WHERE name=? AND username=?";
*/ private static final String INSERT_PROPERTY =
String getUsername(); "INSERT INTO jiveUserProp (username, name, propValue) VALUES (?, ?, ?)";
private String username;
private String name;
private String email;
private Date creationDate;
private Date modificationDate;
private Map<String,String> properties = null;
/** /**
* <p>Sets a new password for the user.</p> * Constructs a new user. All arguments can be <tt>null</tt> except the username.
* Typically, User objects should not be constructed by end-users of the API.
* Instead, user objects should be retrieved using {@link UserManager#getUser(String)}.
* *
* @param password The new password for the user * @param username the username.
* @throws UnauthorizedException If the provider does not allow password changes or the caller does not have permission * @param name the name.
* @param email the email address.
* @param creationDate the date the user was created.
* @param modificationDate the date the user was last modified.
*/ */
void setPassword(String password) throws UnauthorizedException; public User(String username, String name, String email, Date creationDate,
Date modificationDate)
{
if (username == null) {
throw new NullPointerException("Username cannot be null");
}
this.username = username;
this.name = name;
this.email = email;
this.creationDate = creationDate;
this.modificationDate = modificationDate;
}
/** /**
* <p>Returns meta information for this user such as full name, email address, etc.</p> * Returns this user's username.
* *
* @return Meta information about the user * @return the username..
* @throws UserNotFoundException If no information is available for this user
*/ */
UserInfo getInfo() throws UserNotFoundException; public String getUsername() {
return username;
}
/** /**
* <p>Saves the user info associated with this user to persistent storage.</p> * Sets a new password for this user.
* *
* @throws UnauthorizedException If the user info provider does not support the updating of user info * @param password the new password for the user.
*/ */
void saveInfo() throws UnauthorizedException; public void setPassword(String password) {
try {
UserManager.getUserProvider().setPassword(username, password);
}
catch (UserNotFoundException unfe) {
Log.error(unfe);
}
}
public String getName() {
return name;
}
public void setName(String name) {
try {
UserManager.getUserProvider().setName(username, name);
this.name = name;
}
catch (UserNotFoundException unfe) {
Log.error(unfe);
}
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
try {
UserManager.getUserProvider().setEmail(username, email);
this.email = email;
}
catch (UserNotFoundException unfe) {
Log.error(unfe);
}
}
public Date getCreationDate() {
return creationDate;
}
public void setCreationDate(Date creationDate) {
try {
UserManager.getUserProvider().setCreationDate(username, creationDate);
this.creationDate = creationDate;
}
catch (UserNotFoundException unfe) {
Log.error(unfe);
}
}
public Date getModificationDate() {
return modificationDate;
}
public void setModificationDate(Date modificationDate) {
try {
UserManager.getUserProvider().setCreationDate(username, modificationDate);
this.modificationDate = modificationDate;
}
catch (UserNotFoundException unfe) {
Log.error(unfe);
}
}
/** /**
* Returns an extended property of the user. Each user can have an arbitrary number of extended * Returns an extended property of the user. Each user can have an arbitrary number of extended
...@@ -67,7 +164,12 @@ public interface User { ...@@ -67,7 +164,12 @@ public interface User {
* @param name the name of the property to get. * @param name the name of the property to get.
* @return the value of the property * @return the value of the property
*/ */
String getProperty(String name); public String getProperty(String name) {
if (properties == null) {
loadPropertiesFromDb();
}
return properties.get(name);
}
/** /**
* Sets an extended property of the user. Each user can have an arbitrary number of extended * Sets an extended property of the user. Each user can have an arbitrary number of extended
...@@ -77,86 +179,192 @@ public interface User { ...@@ -77,86 +179,192 @@ public interface User {
* *
* @param name the name of the property to set. * @param name the name of the property to set.
* @param value the new value for the property. * @param value the new value for the property.
* @throws UnauthorizedException if not allowed to edit.
*/ */
void setProperty(String name, String value) throws UnauthorizedException; public void setProperty(String name, String value) {
if (properties == null) {
loadPropertiesFromDb();
}
// Make sure the property name and value aren't null.
if (name == null || value == null || "".equals(name) || "".equals(value)) {
throw new NullPointerException("Cannot set property with empty or null value.");
}
// See if we need to update a property value or insert a new one.
if (properties.containsKey(name)) {
// Only update the value in the database if the property value
// has changed.
if (!(value.equals(properties.get(name)))) {
properties.put(name, value);
updatePropertyInDb(name, value);
}
}
else {
properties.put(name, value);
insertPropertyIntoDb(name, value);
}
}
/** /**
* Deletes an extended property. If the property specified by <code>name</code> does not exist, * Deletes an extended property. If the property specified by <code>name</code> does not exist,
* this method will do nothing. * this method will do nothing.
* *
* @param name the name of the property to delete. * @param name the name of the property to delete.
* @throws UnauthorizedException if not allowed to edit.
*/ */
void deleteProperty(String name) throws UnauthorizedException; public void deleteProperty(String name) {
if (properties == null) {
loadPropertiesFromDb();
}
properties.remove(name);
deletePropertyFromDb(name);
}
/** /**
* Returns an Iterator for all the names of the extended user properties. * Returns a Collection of all the names of the extended user properties.
* *
* @return an Iterator for the property names. * @return a Collection of all the property names.
*/ */
Iterator getPropertyNames(); public Collection<String> getPropertyNames() {
if (properties == null) {
loadPropertiesFromDb();
}
return Collections.unmodifiableCollection(properties.keySet());
}
/** /**
* Returns the user's roster. A roster is a list of users that the user wishes to know * Returns the user's roster. A roster is a list of users that the user wishes to know
* if they are online. Rosters are similar to buddy groups in popular IM clients. * if they are online. Rosters are similar to buddy groups in popular IM clients.
* *
* @return the user's roster. * @return the user's roster.
* @throws UnauthorizedException if not the user or an administrator.
*/ */
CachedRoster getRoster() throws UnauthorizedException; public CachedRoster getRoster() {
try {
return BasicServer.getInstance().getRosterManager().getRoster(username);
}
catch (UserNotFoundException unfe) {
Log.error(unfe);
return null;
}
}
/** public int getCachedSize() {
* Returns the permissions for the user that correspond to the passed-in AuthToken. // Approximate the size of the object in bytes by calculating the size
* // of each field.
* @param authToken the auth token to look up permissions with. int size = 0;
*/ size += CacheSizes.sizeOfObject(); // overhead of object
Permissions getPermissions(AuthToken authToken); size += CacheSizes.sizeOfLong(); // id
size += CacheSizes.sizeOfString(username); // username
size += CacheSizes.sizeOfString(email); // email
size += CacheSizes.sizeOfDate() * 2; // creationDate and modificationDate
size += CacheSizes.sizeOfMap(properties); // properties
return size;
}
/** public String toString() {
* Returns true if the handle on the object has the permission specified. A list of possible return username;
* permissions can be found in the Permissions class. Certain methods of this class are }
* restricted to certain permissions as specified in the method comments.
*
* @param permissionType the permission to check for.
* @see Permissions
*/
boolean isAuthorized(long permissionType);
/** public int hashCode() {
* Sets the user's vCard information. Advanced user systems can use vCard return username.hashCode();
* information to link to user directory information or store other }
* relevant user information.
*
* @param name The name of the vcard property
* @param value The value of the vcard property
* @throws UnauthorizedException If the caller doesn't have permission to change the user's vcard
*/
void setVCardProperty(String name, String value) throws UnauthorizedException;
/** public boolean equals(Object object) {
* Obtains the user's vCard information for a given vcard property name. if (this == object) {
* Advanced user systems can use vCard return true;
* information to link to user directory information or store other }
* relevant user information. if (object != null && object instanceof User) {
* return username.equals(((User)object).getUsername());
* @param name The name of the vcard property to retrieve }
* @return The vCard value found else {
*/ return false;
String getVCardProperty(String name); }
}
/** private synchronized void loadPropertiesFromDb() {
* Deletes a given vCard property from the user account. properties = new Hashtable<String,String>();
* Connection con = null;
* @param name The name of the vcard property to remove PreparedStatement pstmt = null;
* @throws UnauthorizedException If not the user or administrator try {
*/ con = DbConnectionManager.getConnection();
void deleteVCardProperty(String name) throws UnauthorizedException; pstmt = con.prepareStatement(LOAD_PROPERTIES);
pstmt.setString(1, username);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
properties.put(rs.getString(1), rs.getString(2));
}
}
catch (SQLException e) {
Log.error(e);
}
finally {
try { if (pstmt != null) { pstmt.close(); } }
catch (Exception e) { Log.error(e); }
try { if (con != null) { con.close(); } }
catch (Exception e) { Log.error(e); }
}
}
/** private void insertPropertyIntoDb(String name, String value) {
* Obtain an iterator for all vcard property names. Connection con = null;
* PreparedStatement pstmt = null;
* @return the iterator over all vcard property names.
*/ try {
Iterator getVCardPropertyNames(); con = DbConnectionManager.getConnection();
} pstmt = con.prepareStatement(INSERT_PROPERTY);
\ No newline at end of file pstmt.setString(1, username);
pstmt.setString(2, name);
pstmt.setString(3, value);
pstmt.executeUpdate();
}
catch (SQLException e) {
Log.error(e);
}
finally {
try { if (pstmt != null) { pstmt.close(); } }
catch (Exception e) { Log.error(e); }
try { if (con != null) { con.close(); } }
catch (Exception e) { Log.error(e); }
}
}
private void updatePropertyInDb(String name, String value) {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(UPDATE_PROPERTY);
pstmt.setString(1, value);
pstmt.setString(2, name);
pstmt.setString(3, username);
pstmt.executeUpdate();
}
catch (SQLException e) {
Log.error(e);
}
finally {
try { if (pstmt != null) { pstmt.close(); } }
catch (Exception e) { Log.error(e); }
try { if (con != null) { con.close(); } }
catch (Exception e) { Log.error(e); }
}
}
private void deletePropertyFromDb(String name) {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(DELETE_PROPERTY);
pstmt.setString(1, username);
pstmt.setString(2, name);
pstmt.executeUpdate();
}
catch (SQLException e) {
Log.error(e);
}
finally {
try { if (pstmt != null) { pstmt.close(); } }
catch (Exception e) { Log.error(e); }
try { if (con != null) { con.close(); } }
catch (Exception e) { Log.error(e); }
}
}
}
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.messenger.user;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.AbstractCollection;
/**
* Provides a view of an array of usernames as a Collection of User objects. If
* any of the usernames cannot be loaded, they are transparently skipped when
* iteratating over the collection.
*
* @author Matt Tucker
*/
public class UserCollection extends AbstractCollection {
private String[] elements;
private int currentIndex = -1;
private Object nextElement = null;
/**
* Constructs a new UserIterator.
*/
public UserCollection(String [] elements) {
this.elements = elements;
}
public Iterator iterator() {
return new UserIterator();
}
public int size() {
return elements.length;
}
private class UserIterator implements Iterator {
public boolean hasNext() {
// If we are at the end of the list, there can't be any more elements
// to iterate through.
if (currentIndex + 1 >= elements.length && nextElement == null) {
return false;
}
// Otherwise, see if nextElement is null. If so, try to load the next
// element to make sure it exists.
if (nextElement == null) {
nextElement = getNextElement();
if (nextElement == null) {
return false;
}
}
return true;
}
public Object next() throws java.util.NoSuchElementException {
Object element = null;
if (nextElement != null) {
element = nextElement;
nextElement = null;
}
else {
element = getNextElement();
if (element == null) {
throw new NoSuchElementException();
}
}
return element;
}
public void remove() throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
/**
* Returns the next available element, or null if there are no more elements to return.
*
* @return the next available element.
*/
private Object getNextElement() {
while (currentIndex + 1 < elements.length) {
currentIndex++;
Object element = null;
try {
element = UserManager.getInstance().getUser(elements[currentIndex]);
}
catch (UserNotFoundException unfe) { }
if (element != null) {
return element;
}
}
return null;
}
}
}
\ No newline at end of file
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.messenger.user;
import org.jivesoftware.messenger.auth.UnauthorizedException;
import org.jivesoftware.util.CacheSizes;
import org.jivesoftware.util.Cacheable;
import org.jivesoftware.util.StringUtils;
/**
* <p>The simplest implementation of UserInfo.</p>
* <p>Useful if you are retrieving user info from a database and just need
* to stuff it into a data structure.</p>
*
* @author Iain Shigeoka
* @author Derek DeMoro
* @see User
*/
public class UserInfo implements Cacheable {
private String username = null;
private String name = " ";
private String email;
private java.util.Date creationDate;
private java.util.Date modificationDate;
/**
* Create a new UserInfo with default fields.
*
* @param username the username.
*/
public UserInfo(String username) {
this.username = username;
creationDate = new java.util.Date();
modificationDate = new java.util.Date();
}
/**
* <p>Create a new UserInfo given field values.</p>
*
* @param username The username
* @param name The user's full name
* @param email The user's email address
*/
public UserInfo(String username, String name, String email,
java.util.Date creationDate, java.util.Date modificationDate)
{
this.username = username;
this.creationDate = creationDate;
this.modificationDate = modificationDate;
if (email == null || "".equals(email)) {
this.email = " ";
}
else {
this.email = email;
}
if (name != null) {
this.name = name;
}
}
public String getUsername() {
return username;
}
public String getName() {
return name;
}
public void setName(String name) throws UnauthorizedException {
if (name == null) {
throw new IllegalArgumentException("Name cannot be null");
}
this.name = name;
// Update modification date
modificationDate.setTime(System.currentTimeMillis());
}
public String getEmail() {
return StringUtils.escapeHTMLTags(email);
}
public void setEmail(String email) throws UnauthorizedException {
if (email == null || "".equals(email)) {
email = " ";
}
this.email = email;
// Update modification date
modificationDate.setTime(System.currentTimeMillis());
}
public java.util.Date getCreationDate() {
return creationDate;
}
public void setCreationDate(java.util.Date creationDate) throws UnauthorizedException {
if (creationDate == null) {
throw new IllegalArgumentException("Creation date cannot be null");
}
this.creationDate.setTime(creationDate.getTime());
}
public java.util.Date getModificationDate() {
return modificationDate;
}
public void setModificationDate(java.util.Date modificationDate) throws UnauthorizedException {
if (modificationDate == null) {
throw new IllegalArgumentException("Modification date cannot be null");
}
this.modificationDate.setTime(modificationDate.getTime());
}
public int getCachedSize() {
// Approximate the size of the object in bytes by calculating the size
// of each field.
int size = 0;
size += CacheSizes.sizeOfObject(); // overhead of object
size += CacheSizes.sizeOfLong(); // id
size += CacheSizes.sizeOfString(name); // name
size += CacheSizes.sizeOfString(email); // email
size += CacheSizes.sizeOfBoolean(); // nameVisible
size += CacheSizes.sizeOfBoolean(); // emailVisible
size += CacheSizes.sizeOfDate(); // creationDate
size += CacheSizes.sizeOfDate(); // modificationDate
return size;
}
/**
* Returns a String representation of the User object using the username.
*
* @return a String representation of the User object.
*/
public String toString() {
return name;
}
public int hashCode() {
return username.hashCode();
}
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object != null && object instanceof User) {
return username.equals(((User)object).getUsername());
}
else {
return false;
}
}
}
\ No newline at end of file
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.messenger.user;
import org.jivesoftware.messenger.auth.UnauthorizedException;
/**
* <p>A common interface to implement when creating a user management service plug-in.</p>
* <p/>
* <p>Provide meta-information about a user that's useful in server behavior. Implementation
* of this provider is optional and systems where user information is stored and managed in
* other systems may want to provide partial implementations or use the Jive dummy implementation
* that returns no values.</p>
* <p/>
* <p>Messenger will cache much of the information it obtains from calling this provider. If you will be modifying
* the underlying data outside of Messenger, please consult with Jive for information on maintaining a valid
* cache.</p>
*
* @author Iain Shigeoka
*/
public interface UserInfoProvider {
/**
* <p>Obtain the UserInfo of a user.</p>
* <p>If your implementation doesn't support user info, simply return a UserInfo object filled with default
* values.</p>
*
* @param username the username of the user.
* @return The user's info
* @throws UserNotFoundException If a user with the given ID couldn't be found
*/
UserInfo getInfo(String username) throws UserNotFoundException;
/**
* <p>Sets the user's info (optional operation).</p>
*
* @param username the username of the user.
* @param info The user's new info
* @throws UserNotFoundException If a user with the given ID couldn't be found
* @throws UnauthorizedException If this operation is not allowed for the caller's permissions
* @throws UnsupportedOperationException If the provider does not support the operation (this is an optional operation)
*/
void setInfo(String username, UserInfo info)
throws UserNotFoundException, UnauthorizedException, UnsupportedOperationException;
}
...@@ -11,45 +11,109 @@ ...@@ -11,45 +11,109 @@
package org.jivesoftware.messenger.user; package org.jivesoftware.messenger.user;
import org.jivesoftware.messenger.container.Module; import org.jivesoftware.messenger.JiveGlobals;
import org.jivesoftware.messenger.auth.UnauthorizedException; import org.jivesoftware.util.Cache;
import java.util.Iterator; import org.jivesoftware.util.CacheManager;
import org.jivesoftware.util.ClassUtils;
import org.jivesoftware.util.Log;
import org.jivesoftware.stringprep.Stringprep;
import org.jivesoftware.stringprep.StringprepException;
import java.util.Collection;
/** /**
* Centralized management of users in the Jive system including creating, retrieving, and deleting * Manages users, including loading, creating and deleting.
* User objects.<p>
* <p/>
* In some cases, you may wish to plug in your own user system implementation. In that case, you
* should set the Jive property <tt>UserManager.className</tt> with the name of your UserManager
* class. Your class must have a public, no-argument constructor. The class must also create and
* return User object implementations as necessary.
* *
* @author Iain Shigeoka * @author Matt Tucker
* @see User * @see User
*/ */
public interface UserManager extends Module { public class UserManager {
private static Cache userCache;
private static UserProvider provider;
private static UserManager instance = new UserManager();
static {
// Initialize caches.
CacheManager.initializeCache("userCache", 512 * 1024);
CacheManager.initializeCache("username2roster", 512 * 1024);
userCache = CacheManager.getCache("userCache");
// Load a group provider.
String className = JiveGlobals.getXMLProperty("provider.user.className",
"org.jivesoftware.messenger.user.DefaultUserProvider");
try {
Class c = ClassUtils.forName(className);
provider = (UserProvider)c.newInstance();
}
catch (Exception e) {
Log.error("Error loading user provider: " + className, e);
provider = new DefaultUserProvider();
}
}
/**
* Returns the currently-installed UserProvider.
*
* @return the current UserProvider.
*/
static UserProvider getUserProvider() {
return provider;
}
public static UserManager getInstance() {
return instance;
}
private UserManager() {
}
/** /**
* Factory method for creating a new User with all required values: a password, and a unique username. * Creates a new User. Required values are username and password. The email address
* can optionally be <tt>null</tt>.
* *
* @param username the new and unique username for the account. * @param username the new and unique username for the account.
* @param password the password for the account as plain text. * @param password the password for the account (plain text).
* @param email The email address to associate with the new account * @param email the email address to associate with the new account, which can
* be <tt>null</tt>.
* @return a new User. * @return a new User.
* @throws UserAlreadyExistsException if the username already exists in the system. * @throws UserAlreadyExistsException if the username already exists in the system.
* @throws UnsupportedOperationException If the provider does not support the operation (this is an optional operation) * @throws UnsupportedOperationException if the provider does not support the
* operation.
*/ */
public User createUser(String username, String password, String email) public User createUser(String username, String password, String name, String email)
throws UserAlreadyExistsException, UnauthorizedException, UnsupportedOperationException; throws UserAlreadyExistsException
{
// Make sure that the username is valid.
try {
username = Stringprep.nameprep(username, true);
}
catch (StringprepException se) {
throw new IllegalArgumentException(se);
}
User user = provider.createUser(username, password, name, email);
userCache.put(username, user);
return user;
}
/** /**
* Deletes a user (optional operation). * Deletes a user (optional operation).
* *
* @param user the user to delete. * @param user the user to delete.
* @throws UnauthorizedException If the caller doesn't have permission to take the given action
* @throws UnsupportedOperationException If the provider does not support the operation (this is an optional operation)
*/ */
public void deleteUser(User user) throws UnauthorizedException, UnsupportedOperationException; public void deleteUser(User user) {
String username = user.getUsername();
// Make sure that the username is valid.
try {
username = Stringprep.nameprep(username, true);
}
catch (StringprepException se) {
throw new IllegalArgumentException(se);
}
provider.deleteUser(user.getUsername());
// Remove the user from cache.
userCache.remove(user.getUsername());
}
/** /**
* Returns the User specified by username. * Returns the User specified by username.
...@@ -58,32 +122,53 @@ public interface UserManager extends Module { ...@@ -58,32 +122,53 @@ public interface UserManager extends Module {
* @return the User that matches <tt>username</tt>. * @return the User that matches <tt>username</tt>.
* @throws UserNotFoundException if the user does not exist. * @throws UserNotFoundException if the user does not exist.
*/ */
public User getUser(String username) throws UserNotFoundException; public User getUser(String username) throws UserNotFoundException {
// Make sure that the username is valid.
try {
username = Stringprep.nameprep(username, true);
}
catch (StringprepException se) {
throw new IllegalArgumentException(se);
}
User user = (User)userCache.get(username);
if (user == null) {
user = provider.loadUser(username);
userCache.put(username, user);
}
return user;
}
/** /**
* Returns the number of users in the system. * Returns the total number of users in the system.
* *
* @return the total number of users. * @return the total number of users.
*/ */
public int getUserCount(); public int getUserCount() {
return provider.getUserCount();
}
/** /**
* Returns an iterator for all users in the system. * Returns an unmodifiable Collection of all users in the system.
* *
* @return an Iterator for all users. * @return an unmodifiable Collection of all users.
*/ */
public Iterator users() throws UnauthorizedException; public Collection<User> getUsers() {
return provider.getUsers();
}
/** /**
* Returns an iterator for all users starting at <tt>startIndex</tt> with the given number of * Returns an unmodifiable Collection of all users starting at <tt>startIndex</tt>
* results. This is useful to support pagination in a GUI where you may only want to display a * with the given number of results. This is useful to support pagination in a GUI
* certain number of results per page. It is possible that the number of results returned will * where you may only want to display a certain number of results per page. It is
* be less than that specified by numResults if numResults is greater than the number of records * possible that the number of results returned will be less than that specified
* left in the system to display. * by <tt>numResults</tt> if <tt>numResults</tt> is greater than the number of
* records left to display.
* *
* @param startIndex the beginning index to start the results at. * @param startIndex the beginning index to start the results at.
* @param numResults the total number of results to return. * @param numResults the total number of results to return.
* @return an Iterator for all users in the specified range. * @return a Collection of users in the specified range.
*/ */
public Iterator users(int startIndex, int numResults) throws UnauthorizedException; public Collection<User> getUsers(int startIndex, int numResults) {
return provider.getUsers(startIndex, numResults);
}
} }
\ No newline at end of file
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.messenger.user;
import org.jivesoftware.messenger.auth.UnauthorizedException;
import java.util.Map;
/**
* <p>Implement this provider to store user and vcard properties somewhere
* other than the Jive tables, or to capture jive property events.</p>
* <p/>
* <p>Implementors: Most integrators will not need to create their own
* user property providers. In almost all cases, it's best to let Messenger
* store these values in the Jive tables.</p>
*
* @author Iain Shigeoka
*/
public interface UserPropertiesProvider {
/**
* <p>Delete a user's vcard property (optional operation).</p>
*
* @param username the username of the user
* @param name The name of the property to delete
* @throws UnauthorizedException If the caller does not have permission to carry out the operation
* @throws UnsupportedOperationException If the provider does not support the operation (this is an optional operation)
*/
public void deleteVcardProperty(String username, String name) throws UnauthorizedException, UnsupportedOperationException;
/**
* <p>Delete a user's user property (optional operation).</p>
*
* @param username the username of the user
* @param name The name of the property to delete
* @throws UnauthorizedException If the caller does not have permission to carry out the operation
* @throws UnsupportedOperationException If the provider does not support the operation (this is an optional operation)
*/
public void deleteUserProperty(String username, String name) throws UnauthorizedException, UnsupportedOperationException;
/**
* <p>Insert a new vcard property (optional operation).</p>
*
* @param username the username of the user
* @param name The name of the property
* @param value The value of the property
* @throws UnauthorizedException If the caller does not have permission to carry out the operation
* @throws UnsupportedOperationException If the provider does not support the operation (this is an optional operation)
*/
public void insertVcardProperty(String username, String name, String value) throws UnauthorizedException, UnsupportedOperationException;
/**
* <p>Insert a new user property (optional operation).</p>
*
* @param username the username of the user
* @param name The name of the property
* @param value The value of the property
* @throws UnauthorizedException If the caller does not have permission to carry out the operation
* @throws UnsupportedOperationException If the provider does not support the operation (this is an optional operation)
*/
public void insertUserProperty(String username, String name, String value) throws UnauthorizedException, UnsupportedOperationException;
/**
* <p>Update a vcard property (optional operation).</p>
*
* @param username the username of the user.
* @param name The name of the property.
* @param value The value of the property.
* @throws UnauthorizedException if the caller does not have permission to carry out the operation
* @throws UnsupportedOperationException if the provider does not support the operation (this is an optional operation).
*/
public void updateVcardProperty(String username, String name, String value)
throws UnauthorizedException, UnsupportedOperationException;
/**
* <p>Update an Existing user property (optional operation).</p>
*
* @param username the username of the user
* @param name The name of the property
* @param value The value of the property
* @throws UnauthorizedException If the caller does not have permission to carry out the operation
* @throws UnsupportedOperationException If the provider does not support the operation (this is an optional operation)
*/
public void updateUserProperty(String username, String name, String value) throws UnauthorizedException, UnsupportedOperationException;
/**
* <p>Obtain a map containing all vcard properties for a user.</p>
* <p/>
* <p>If the provider doesn't support vcard properties, return an empty map.</p>
*
* @param username the username of the user to retrieve the vcard properties
* @return A map of property name-value pairs
*/
public Map getVcardProperties(String username);
/**
* <p>Obtain a map containing all user properties for a user.</p>
* <p/>
* <p>If the provider doesn't support user properties, return an empty map.</p>
*
* @param username the username of the user to retrieve the user properties
* @return A map of property name-value pairs
*/
public Map getUserProperties(String username);
}
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.messenger.user;
import java.util.Date;
import java.util.Collection;
/**
* Provider interface for the user system.
*
* @author Matt Tucker
*/
public interface UserProvider {
/**
* Loads the specified user by username.
*
* @param username the username
* @return the User.
* @throws UserNotFoundException if the User could not be loaded.
*/
User loadUser(String username) throws UserNotFoundException;
/**
* Creates a new user. This method should throw an
* UnsupportedOperationException if this operation is not
* supporte by the backend user store.
*
* @param username the username.
* @param password the plain-text password.
* @param name the user's name, which can be <tt>null</tt>.
* @param email the user's email address, which can be <tt>null</tt>.
* @return a new User.
* @throws UserAlreadyExistsException if the username is already in use.
*/
User createUser(String username, String password, String name, String email)
throws UserAlreadyExistsException;
/**
* Delets a user. This method should throw an
* UnsupportedOperationException if this operation is not
* supported by the backend user store.
*
* @param username the username to delete.
*/
void deleteUser(String username);
/**
* Returns the number of users in the system.
*
* @return the total number of users.
*/
public int getUserCount();
/**
* Returns an unmodifiable Collections of all users in the system. The
* {@link UserCollection} class can be used to assist in the implementation
* of this method. It takes a String [] of usernames and presents it as a
* Collection of User objects (obtained with calls to
* {@link UserManager#getUser(String)}.
*
* @return an unmodifiable Collection of all users.
*/
public Collection<User> getUsers();
/**
* Returns an unmodifiable Collections of users in the system within the
* specified range. The {@link UserCollection} class can be used to assist
* in the implementation of this method. It takes a String [] of usernames
* and presents it as a Collection of User objects (obtained with calls to
* {@link UserManager#getUser(String)}.<p>
*
* It is possible that the number of results returned will be less than that
* specified by <tt>numResults</tt> if <tt>numResults</tt> is greater than the
* number of records left to display.
*
* @param startIndex the beginning index to start the results at.
* @param numResults the total number of results to return.
* @return an unmodifiable Collection of users within the specified range.
*/
public Collection<User> getUsers(int startIndex, int numResults);
/**
* Sets the users's password. This method should throw an UnsupportedOperationException
* if this operation is not supported by the backend user store.
*
* @param username the username of the user.
* @param password the new plaintext password for the user.
* @throws UserNotFoundException if the given user could not be loaded.
* @throws UnsupportedOperationException if the provider does not
* support the operation (this is an optional operation).
*/
public void setPassword(String username, String password)
throws UserNotFoundException;
/**
* Sets the user's name. This method should throw an UnsupportedOperationException
* if this operation is not supported by the backend user store.
*
* @param username the username.
* @param name the name.
* @throws UserNotFoundException if the user could not be found.
*/
public void setName(String username, String name) throws UserNotFoundException;
/**
* Sets the user's email address. This method should throw an
* UnsupportedOperationException if this operation is not supported
* by the backend user store.
*
* @param username the username.
* @param email the email address.
* @throws UserNotFoundException if the user could not be found.
*/
public void setEmail(String username, String email) throws UserNotFoundException;
/**
* Sets the date the user was created. This method should throw an
* UnsupportedOperationException if this operation is not supported
* by the backend user store.
*
* @param username the username.
* @param creationDate the date the user was created.
* @throws UserNotFoundException if the user could not be found.
*/
public void setCreationDate(String username, Date creationDate) throws UserNotFoundException;
/**
* Sets the date the user was last modified. This method should throw an
* UnsupportedOperationException if this operation is not supported
* by the backend user store.
*
* @param username the username.
* @param modificationDate the date the user was last modified.
* @throws UserNotFoundException if the user could not be found.
*/
public void setModificationDate(String username, Date modificationDate) throws UserNotFoundException;
}
\ No newline at end of file
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.messenger.user;
import org.jivesoftware.util.ClassUtils;
import org.jivesoftware.util.Log;
import org.jivesoftware.messenger.JiveGlobals;
/**
* <p>Provides a centralized source of the various user providers.</p>
* <p/>
* <p>The user system has many providers. These providers allow you to
* integrate Messenger with various backend user management systems on a
* 'pay as you go' basis. In otherwords, a simple integration only requires
* a very small amount of customization (modifying a few providers) while
* a more complex integration can modify many providers.</p>
* <p/>
* <p>Users of Jive that wish to change the User*Provider implementation used to generate
* users can set the <code>UserProvider.*.className</code> Jive properties. For example, if
* you have altered Jive to use LDAP for user information, you'd want to send a custom
* implementation of User*Provider classes to make LDAP user queries. After changing the
* <code>UserProvider.*.className</code> Jive properties, you must restart Messenger. The
* valid properties are:<p>
* <p/>
* <ul>
* <li>UserProvider.id.className - specifies a UserIDProvider class.</li>
* <li>UserProvider.properties.className - specifies a UserPropertiesProvider class.</li>
* <li>UserProvider.info.className - specifies a UserInfoProvider class.</li>
* <li>UserProvider.account.className - specifies a UserAccountProvider class.</li>
* </ul>
*
* @author Iain Shigeoka
*/
public class UserProviderFactory {
private static UserPropertiesProvider userPropertiesProvider;
private static UserInfoProvider userInfoProvider;
private static RosterItemProvider rosterItemProvider;
/**
* The default class to instantiate is database implementation.
*/
private static String[] classNames = {"org.jivesoftware.messenger.user.spi.DbUserPropertiesProvider",
"org.jivesoftware.messenger.user.spi.DbUserInfoProvider",
"org.jivesoftware.messenger.user.spi.DbRosterItemProvider"};
private static String[] propNames = {"UserProvider.id.className",
"UserProvider.properties.className",
"UserProvider.info.className",
"UserProvider.account.className",
"UserProvider.roster.className"};
private static void setProviders(Class[] providers) throws IllegalAccessException, InstantiationException {
userPropertiesProvider = (UserPropertiesProvider)providers[0].newInstance();
userInfoProvider = (UserInfoProvider)providers[1].newInstance();
rosterItemProvider = (RosterItemProvider)providers[2].newInstance();
}
public static UserPropertiesProvider getUserPropertiesProvider() {
loadProviders();
return userPropertiesProvider;
}
public static UserInfoProvider getUserInfoProvider() {
loadProviders();
return userInfoProvider;
}
public static RosterItemProvider getRosterItemProvider() {
loadProviders();
return rosterItemProvider;
}
private static void loadProviders() {
if (userInfoProvider == null) {
// Use className as a convenient object to get a lock on.
synchronized (classNames) {
if (userInfoProvider == null) {
try {
Class[] providers = new Class[classNames.length];
for (int i = 0; i < classNames.length; i++) {
String className = classNames[i];
//See if the classname has been set as a Jive property.
String classNameProp = JiveGlobals.getXMLProperty(propNames[i]);
if (classNameProp != null) {
className = classNameProp;
}
try {
providers[i] = ClassUtils.forName(className);
}
catch (Exception e) {
Log.error("Exception loading class: " + className, e);
}
}
setProviders(providers);
}
catch (Exception e) {
Log.error("Exception loading class: " + classNames, e);
}
}
}
}
}
}
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.messenger.user.spi;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.messenger.auth.UnauthorizedException;
import org.jivesoftware.messenger.user.User;
import org.jivesoftware.messenger.user.UserInfo;
import org.jivesoftware.messenger.user.UserInfoProvider;
import org.jivesoftware.messenger.user.UserNotFoundException;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.StringUtils;
/**
* Catabase implementation of the UserInfoProvider interface.
*
* @author Matt Tucker
* @author Bruce Ritchie
* @author Iain Shigeoka
*
* @see User
*/
public class DbUserInfoProvider implements UserInfoProvider {
private static final String LOAD_USER_BY_USERNAME =
"SELECT name, email, creationDate, modificationDate FROM jiveUser WHERE username=?";
public UserInfo getInfo(String username) throws UserNotFoundException {
UserInfo userInfo = null;
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(LOAD_USER_BY_USERNAME);
pstmt.setString(1, username);
ResultSet rs = pstmt.executeQuery();
if (!rs.next()) {
throw new UserNotFoundException();
}
// We trim() the dates before trying to parse them because some
// databases pad with extra characters when returning the data.
userInfo = new UserInfo(username,
rs.getString(1), // name
rs.getString(2), // email
new java.util.Date(Long.parseLong(rs.getString(3).trim())), // creation date
new java.util.Date(Long.parseLong(rs.getString(4).trim()))); // modification date
}
catch (SQLException e) {
throw new UserNotFoundException("Failed to read user " + username + " from database.", e);
}
catch (NumberFormatException nfe) {
Log.error("WARNING: There was an error parsing the dates " +
"returned from the database. Ensure that they're being stored " +
"correctly.");
throw new UserNotFoundException("User "
+ username + " could not be loaded from the database.");
}
finally {
try { if (pstmt != null) { pstmt.close(); } }
catch (Exception e) { Log.error(e); }
try { if (con != null) { con.close(); } }
catch (Exception e) { Log.error(e); }
}
return userInfo;
}
private static final String SAVE_USER =
"UPDATE jiveUser SET name=?,email=?,creationDate=?,modificationDate=? " +
"WHERE username=?";
public void setInfo(String username, UserInfo info) throws UserNotFoundException, UnauthorizedException {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(SAVE_USER);
pstmt.setString(1, info.getName());
pstmt.setString(2, info.getEmail());
pstmt.setString(3, StringUtils.dateToMillis(info.getCreationDate()));
pstmt.setString(4, StringUtils.dateToMillis(info.getModificationDate()));
pstmt.setString(5, username);
pstmt.executeUpdate();
}
catch (SQLException e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
throw new UnauthorizedException();
}
finally {
try { if (pstmt != null) { pstmt.close(); } }
catch (Exception e) { Log.error(e); }
try { if (con != null) { con.close(); } }
catch (Exception e) { Log.error(e); }
}
}
}
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.messenger.user.spi;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import org.jivesoftware.messenger.auth.AuthProvider;
import org.jivesoftware.messenger.auth.AuthProviderFactory;
import org.jivesoftware.messenger.auth.AuthToken;
import org.jivesoftware.messenger.auth.Permissions;
import org.jivesoftware.messenger.auth.UnauthorizedException;
import org.jivesoftware.messenger.user.CachedRoster;
import org.jivesoftware.messenger.user.User;
import org.jivesoftware.messenger.user.UserInfo;
import org.jivesoftware.messenger.user.UserInfoProvider;
import org.jivesoftware.messenger.user.UserNotFoundException;
import org.jivesoftware.messenger.user.UserPropertiesProvider;
import org.jivesoftware.messenger.user.UserProviderFactory;
import org.jivesoftware.util.CacheManager;
import org.jivesoftware.util.CacheSizes;
import org.jivesoftware.util.Cacheable;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log;
/**
* <p>Database implementation of the UserInfoProvider interface.</p>
*
* @author Matt Tucker
* @author Bruce Ritchie
* @author Iain Shigeoka
* @see User
*/
public class UserImpl implements User, Cacheable {
/**
* Controls whether extended properties should be lazily loaded (not loaded
* until requested). If the properties are infrequently used, this provides
* a great speedup in initial object loading time. However, if your
* application does use extended properties all the time, you may wish to
* turn lazy loading off, as it's actually faster in total db lookup time
* to load everything at once.
*/
private static final boolean LAZY_PROP_LOADING = true;
private static final Permissions USER_ADMIN_PERMS = new Permissions(Permissions.USER_ADMIN);
UserPropertiesProvider propertiesProvider = UserProviderFactory.getUserPropertiesProvider();
UserInfoProvider infoProvider = UserProviderFactory.getUserInfoProvider();
AuthProvider authProvider = AuthProviderFactory.getAuthProvider();
/**
* User id of -1 is reserved for "anonymous user" and 0 is reserved for "all users".
*/
private String username = null;
private UserInfo userInfo;
private Map properties;
private Map vcardProps;
/**
* Create a new DbUser with all required fields.
*
* @param username the username for the user.
*/
protected UserImpl(String username) {
this.username = username;
}
public String getUsername() {
return username;
}
public void setPassword(String password) throws UnauthorizedException {
try {
authProvider.updatePassword(username, password);
}
catch (UserNotFoundException e) {
throw new UnauthorizedException(e);
}
}
public UserInfo getInfo() throws UserNotFoundException {
if (userInfo == null) {
userInfo = infoProvider.getInfo(username);
}
return userInfo;
}
public void saveInfo() throws UnauthorizedException {
if (userInfo != null) {
try {
infoProvider.setInfo(username, userInfo);
}
catch (UserNotFoundException e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
}
}
}
public String getProperty(String name) {
return getProp(name, false);
}
private String getProp(String name, boolean isVcard) {
if (LAZY_PROP_LOADING && properties == null) {
loadPropertiesFromDb(isVcard);
}
if (isVcard) {
return (String)vcardProps.get(name);
}
else {
return (String)properties.get(name);
}
}
private void loadPropertiesFromDb(boolean isVcard) {
if (isVcard) {
vcardProps = propertiesProvider.getVcardProperties(username);
}
else {
properties = propertiesProvider.getUserProperties(username);
}
}
public void setProperty(String name, String value) throws UnauthorizedException {
setProp(name, value, false);
}
private void setProp(String name, String value, boolean isVcard) throws UnauthorizedException {
if (LAZY_PROP_LOADING && properties == null) {
loadPropertiesFromDb(isVcard);
}
// Make sure the property name and value aren't null.
if (name == null || value == null || "".equals(name) || "".equals(value)) {
throw new NullPointerException("Cannot set property with empty or null value.");
}
Map props;
if (isVcard) {
props = vcardProps;
}
else {
props = properties;
}
// See if we need to update a property value or insert a new one.
if (props.containsKey(name)) {
// Only update the value in the database if the property value
// has changed.
if (!(value.equals(props.get(name)))) {
props.put(name, value);
updatePropertyInDb(name, value, isVcard);
// Re-add user to cache.
CacheManager.getCache("userCache").put(username, this);
}
}
else {
props.put(name, value);
insertPropertyIntoDb(name, value, isVcard);
// Re-add user to cache.
CacheManager.getCache("userCache").put(username, this);
}
}
private void insertPropertyIntoDb(String name, String value, boolean vcard) throws UnauthorizedException {
if (vcard) {
propertiesProvider.insertVcardProperty(username, name, value);
}
else {
propertiesProvider.insertUserProperty(username, name, value);
}
}
private void updatePropertyInDb(String name, String value, boolean vcard) throws UnauthorizedException {
if (vcard) {
propertiesProvider.updateVcardProperty(username, name, value);
}
else {
propertiesProvider.updateUserProperty(username, name, value);
}
}
public void deleteProperty(String name) throws UnauthorizedException {
deleteProp(name, false);
}
private void deleteProp(String name, boolean isVcard) throws UnauthorizedException {
if (LAZY_PROP_LOADING && properties == null) {
loadPropertiesFromDb(isVcard);
}
Map props;
if (isVcard) {
props = vcardProps;
}
else {
props = properties;
}
// Only delete the property if it exists.
if (props.containsKey(name)) {
props.remove(name);
deletePropertyFromDb(name, isVcard);
// Re-add user to cache.
CacheManager.getCache("userCache").put(username, this);
}
}
private void deletePropertyFromDb(String name, boolean vcard) throws UnauthorizedException {
if (vcard) {
propertiesProvider.deleteVcardProperty(username, name);
}
else {
propertiesProvider.deleteUserProperty(username, name);
}
}
public Iterator getPropertyNames() {
return getPropNames(false);
}
private Iterator getPropNames(boolean isVcard) {
if (LAZY_PROP_LOADING && properties == null) {
loadPropertiesFromDb(isVcard);
}
if (isVcard) {
return Collections.unmodifiableSet(vcardProps.keySet()).iterator();
}
else {
return Collections.unmodifiableSet(properties.keySet()).iterator();
}
}
public CachedRoster getRoster() throws UnauthorizedException {
CachedRoster roster = null;
if (CacheManager.getCache("username2roster").get(username) != null) {
// Check for a cached roster:
roster = (CachedRoster)CacheManager.getCache("username2roster").get(username);
}
else {
// Not in cache so load a new one:
roster = new CachedRosterImpl(username);
CacheManager.getCache("username2roster").put(username, roster);
}
return roster;
}
public Permissions getPermissions(AuthToken auth) {
if (auth.getUsername() == username) {
return USER_ADMIN_PERMS;
}
else {
return new Permissions(Permissions.NONE);
}
}
public boolean isAuthorized(long permissionType) {
return true;
}
public void setVCardProperty(String name, String value) throws UnauthorizedException {
setProp(name, value, true);
}
public String getVCardProperty(String name) {
return getProp(name, true);
}
public void deleteVCardProperty(String name) throws UnauthorizedException {
deleteProp(name, true);
}
public Iterator getVCardPropertyNames() {
return getPropNames(true);
}
public int getCachedSize() {
// Approximate the size of the object in bytes by calculating the size
// of each field.
int size = 0;
size += CacheSizes.sizeOfObject(); // overhead of object
size += CacheSizes.sizeOfLong(); // id
size += CacheSizes.sizeOfString(username); // username
if (userInfo != null) {
size += userInfo.getCachedSize();
}
size += CacheSizes.sizeOfMap(properties); // properties
size += CacheSizes.sizeOfMap(vcardProps); // vcard properties
return size;
}
/**
* Returns a String representation of the User object using the username.
*
* @return a String representation of the User object.
*/
public String toString() {
return username;
}
public int hashCode() {
return username.hashCode();
}
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object != null && object instanceof User) {
return username.equals(((User)object).getUsername());
}
else {
return false;
}
}
}
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.messenger.user.spi;
import java.util.Iterator;
import java.util.NoSuchElementException;
import org.jivesoftware.messenger.user.User;
import org.jivesoftware.messenger.user.UserManager;
import org.jivesoftware.messenger.user.UserNotFoundException;
import org.jivesoftware.messenger.spi.BasicServer;
/**
* An class that defines the logic to iterate through an array of long unique ID's of Jive objects.
* <p/>
* <p/>
* One feature of the class is the ability to recover from underlying modifications to the dataset
* in some cases. Consider the following sequence of events:
* <ul>
* <li> Time 00: An Iterator for users in a group is obtained.
* <li> Time 01: 3 of the 8 users in the group are deleted.
* <li> Time 02: Iteration of users begins.
* </ul>
* <p/>
* In the above example, the underlying users in the group were changed after the initial
* iterator was obtained. The logic in this class will attempt to automatically compensate for
* these changes by skipping over items that cannot be loaded. In the above example, that would
* translate to the iterator returning 5 users instead of 8.
*
* @author Iain Shigeoka
*/
public class UserIterator implements Iterator {
private String[] elements;
private int currentIndex = -1;
private Object nextElement = null;
private UserDOFactory objectFactory;
/**
* Creates a new UserIterator.
*/
public UserIterator(String[] elements) {
this.elements = elements;
// Create an objectFactory to load users.
this.objectFactory = new UserDOFactory();
}
private class UserDOFactory {
private UserManager userManager;
public UserDOFactory() {
userManager = BasicServer.getInstance().getUserManager();
}
public Object loadObject(String username) {
try {
User user = userManager.getUser(username);
return user;
}
catch (UserNotFoundException e) { /* ignore */
}
return null;
}
}
/**
* Returns true if there are more elements in the iteration.
*
* @return true if the iterator has more elements.
*/
public boolean hasNext() {
// If we are at the end of the list, there can't be any more elements
// to iterate through.
if (currentIndex + 1 >= elements.length && nextElement == null) {
return false;
}
// Otherwise, see if nextElement is null. If so, try to load the next
// element to make sure it exists.
if (nextElement == null) {
nextElement = getNextElement();
if (nextElement == null) {
return false;
}
}
return true;
}
/**
* Returns the next element.
*
* @return the next element.
* @throws java.util.NoSuchElementException
* if there are no more elements.
*/
public Object next() throws java.util.NoSuchElementException {
Object element = null;
if (nextElement != null) {
element = nextElement;
nextElement = null;
}
else {
element = getNextElement();
if (element == null) {
throw new NoSuchElementException();
}
}
return element;
}
/**
* Not supported for security reasons.
*/
public void remove() throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
/**
* Returns the next available element, or null if there are no more elements to return.
*
* @return the next available element.
*/
public Object getNextElement() {
while (currentIndex + 1 < elements.length) {
currentIndex++;
Object element = objectFactory.loadObject(elements[currentIndex]);
if (element != null) {
return element;
}
}
return null;
}
}
\ No newline at end of file
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
package org.jivesoftware.util; package org.jivesoftware.util;
import org.jivesoftware.messenger.auth.AuthToken; import org.jivesoftware.messenger.auth.AuthToken;
import org.jivesoftware.messenger.auth.Permissions; import org.jivesoftware.messenger.Permissions;
/** /**
* An interface that defines a method to create proxy objects based on an authToken and permissions. * An interface that defines a method to create proxy objects based on an authToken and permissions.
......
...@@ -15,12 +15,13 @@ import org.jivesoftware.messenger.muc.MultiUserChatServer; ...@@ -15,12 +15,13 @@ import org.jivesoftware.messenger.muc.MultiUserChatServer;
import org.jivesoftware.messenger.auth.AuthToken; import org.jivesoftware.messenger.auth.AuthToken;
import org.jivesoftware.messenger.user.User; import org.jivesoftware.messenger.user.User;
import org.jivesoftware.messenger.user.UserManager; import org.jivesoftware.messenger.user.UserManager;
import org.jivesoftware.messenger.user.RosterManager; import org.jivesoftware.messenger.roster.RosterManager;
import org.jivesoftware.messenger.XMPPServer; import org.jivesoftware.messenger.XMPPServer;
import org.jivesoftware.messenger.PrivateStorage; import org.jivesoftware.messenger.PrivateStorage;
import org.jivesoftware.messenger.PresenceManager; import org.jivesoftware.messenger.PresenceManager;
import org.jivesoftware.messenger.SessionManager; import org.jivesoftware.messenger.SessionManager;
import org.jivesoftware.messenger.XMPPServerInfo; import org.jivesoftware.messenger.XMPPServerInfo;
import org.jivesoftware.messenger.roster.RosterManager;
import org.jivesoftware.messenger.spi.BasicServer; import org.jivesoftware.messenger.spi.BasicServer;
import org.jivesoftware.messenger.group.GroupManager; import org.jivesoftware.messenger.group.GroupManager;
......
...@@ -105,15 +105,16 @@ ...@@ -105,15 +105,16 @@
<% } %> <% } %>
<table class="box" cellpadding="3" cellspacing="1" border="0" width="600"> <p>Use the form below to create a new group.</p>
<form name="f" action="group-create.jsp" method="post">
<tr><td class="text" colspan="2">
Use the form below to create a new group in the system.
</td></tr>
<tr class="jive-even"> <form name="f" action="group-create.jsp" method="post">
<fieldset>
<legend>Create New Group</legend>
<div>
<table class="box" cellpadding="3" cellspacing="1" border="0" width="600">
<tr valign="middle" class="jive-even">
<td> <td>
Group: * Group Name: *
</td> </td>
<td> <td>
<input type="text" name="name" size="30" maxlength="75" <input type="text" name="name" size="30" maxlength="75"
...@@ -134,7 +135,7 @@ Use the form below to create a new group in the system. ...@@ -134,7 +135,7 @@ Use the form below to create a new group in the system.
<% } %> <% } %>
</td> </td>
</tr> </tr>
<tr class="jive-odd"> <tr valign="middle" class="jive-odd">
<td> <td>
Description: Description:
</td> </td>
...@@ -152,11 +153,12 @@ Use the form below to create a new group in the system. ...@@ -152,11 +153,12 @@ Use the form below to create a new group in the system.
</td> </td>
</tr> </tr>
</table> </table>
<br>
* Required fields
</div> </div>
</fieldset>
<p> <br><br>
* Required fields
</p>
<input type="submit" name="create" value="Create Group"> <input type="submit" name="create" value="Create Group">
<input type="submit" name="cancel" value="Cancel"> <input type="submit" name="cancel" value="Cancel">
...@@ -167,7 +169,7 @@ Use the form below to create a new group in the system. ...@@ -167,7 +169,7 @@ Use the form below to create a new group in the system.
document.f.name.focus(); document.f.name.focus();
function checkFields() { function checkFields() {
} }
</script> </script>
......
...@@ -47,7 +47,7 @@ ...@@ -47,7 +47,7 @@
%> %>
<p> <p>
Below is a list of groups in the system. Below is a list of the groups in the system.
</p> </p>
<% if (request.getParameter("deletesuccess") != null) { %> <% if (request.getParameter("deletesuccess") != null) { %>
...@@ -72,7 +72,6 @@ Total Groups: <%= webManager.getGroupManager().getGroupCount() %>, ...@@ -72,7 +72,6 @@ Total Groups: <%= webManager.getGroupManager().getGroupCount() %>,
Showing <%= (start+1) %>-<%= (start+range) %>, Showing <%= (start+1) %>-<%= (start+range) %>,
<% } %> <% } %>
Sorted by Group Name
</p> </p>
<% if (numPages > 1) { %> <% if (numPages > 1) { %>
...@@ -125,7 +124,7 @@ Sorted by Group Name ...@@ -125,7 +124,7 @@ Sorted by Group Name
i++; i++;
%> %>
<tr class="jive-<%= (((i%2)==0) ? "even" : "odd") %>"> <tr class="jive-<%= (((i%2)==0) ? "even" : "odd") %>">
<td width="1%"> <td width="1%" valign="top">
<%= i %> <%= i %>
</td> </td>
<td width="60%"> <td width="60%">
...@@ -144,7 +143,7 @@ Sorted by Group Name ...@@ -144,7 +143,7 @@ Sorted by Group Name
<%= group.getAdministrators().size() %> <%= group.getAdministrators().size() %>
</td> </td>
<td width="1%" align="center"> <td width="1%" align="center">
<a href="user-edit-form.jsp?group=<%= group.getName() %>" <a href="group-edit-form.jsp?group=<%= group.getName() %>"
title="Click to edit..." title="Click to edit..."
><img src="images/edit-16x16.gif" width="17" height="17" border="0"></a> ><img src="images/edit-16x16.gif" width="17" height="17" border="0"></a>
</td> </td>
......
...@@ -76,7 +76,7 @@ ...@@ -76,7 +76,7 @@
throw new UnauthorizedException("Only user 'admin' may login."); throw new UnauthorizedException("Only user 'admin' may login.");
} }
} }
authToken = AuthFactory.getAuthToken(username, password); authToken = AuthFactory.authenticate(username, password);
session.setAttribute("jive.admin.authToken", authToken); session.setAttribute("jive.admin.authToken", authToken);
response.sendRedirect(go(url)); response.sendRedirect(go(url));
return; return;
......
...@@ -124,7 +124,7 @@ There are several options for controlling how much history to store for each roo ...@@ -124,7 +124,7 @@ There are several options for controlling how much history to store for each roo
<div> <div>
<table cellpadding="3" cellspacing="0" border="0" width="100%"> <table cellpadding="3" cellspacing="0" border="0" width="100%">
<tbody> <tbody>
<tr valign="top" class=""> <tr valign="middle" class="">
<td width="1%" nowrap> <td width="1%" nowrap>
<input type="radio" name="policy" value="<%= NONE %>" id="rb01" <%= ((policy==NONE) ? "checked" : "") %> /> <input type="radio" name="policy" value="<%= NONE %>" id="rb01" <%= ((policy==NONE) ? "checked" : "") %> />
</td> </td>
...@@ -134,7 +134,7 @@ There are several options for controlling how much history to store for each roo ...@@ -134,7 +134,7 @@ There are several options for controlling how much history to store for each roo
</label>- Do not show a chat history to users joining a room. </label>- Do not show a chat history to users joining a room.
</td> </td>
</tr> </tr>
<tr valign="top"> <tr valign="middle">
<td width="1%" nowrap> <td width="1%" nowrap>
<input type="radio" name="policy" value="<%= ALL %>" id="rb02" <%= ((policy==ALL) ? "checked" : "") %>/> <input type="radio" name="policy" value="<%= ALL %>" id="rb02" <%= ((policy==ALL) ? "checked" : "") %>/>
</td> </td>
...@@ -154,7 +154,7 @@ There are several options for controlling how much history to store for each roo ...@@ -154,7 +154,7 @@ There are several options for controlling how much history to store for each roo
</label>- Show a specific number of the most recent messages in the chat. Use the box below to specify that number. </label>- Show a specific number of the most recent messages in the chat. Use the box below to specify that number.
</td> </td>
</tr> </tr>
<tr valign="top" class=""> <tr valign="middle" class="">
<td width="1%" nowrap>&nbsp;</td> <td width="1%" nowrap>&nbsp;</td>
<td width="99%"> <td width="99%">
<input type="text" name="numMessages" size="5" maxlength="10" onclick="this.form.policy[2].checked=true;" value="<%= ((numMessages > 0) ? ""+numMessages : "") %>"/> messages <input type="text" name="numMessages" size="5" maxlength="10" onclick="this.form.policy[2].checked=true;" value="<%= ((numMessages > 0) ? ""+numMessages : "") %>"/> messages
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<%@ page import="org.jivesoftware.util.*, <%@ page import="org.jivesoftware.util.*,
org.jivesoftware.messenger.SessionManager, org.jivesoftware.messenger.SessionManager,
java.util.Iterator, java.util.*,
org.jivesoftware.messenger.Session" %> org.jivesoftware.messenger.Session" %>
<%@ include file="global.jsp" %> <%@ include file="global.jsp" %>
...@@ -106,8 +106,9 @@ their username in the box below. ...@@ -106,8 +106,9 @@ their username in the box below.
</td> </td>
<td width="98%"> <td width="98%">
<select size="1" name="usernameSEL" onfocus="this.form.choose[1].checked=true;"> <select size="1" name="usernameSEL" onfocus="this.form.choose[1].checked=true;">
<% for (Iterator users=userManager.users(); users.hasNext(); ) { <%
User user = (User)users.next(); Collection<User> users = userManager.getUsers();
for (User user: users) {
%> %>
<option value="<%= user.getUsername() %>"><%= user.getUsername() %></option> <option value="<%= user.getUsername() %>"><%= user.getUsername() %></option>
......
...@@ -14,8 +14,7 @@ ...@@ -14,8 +14,7 @@
org.jivesoftware.messenger.auth.AuthFactory, org.jivesoftware.messenger.auth.AuthFactory,
org.jivesoftware.messenger.auth.AuthToken, org.jivesoftware.messenger.auth.AuthToken,
org.jivesoftware.messenger.JiveGlobals, org.jivesoftware.messenger.JiveGlobals,
org.jivesoftware.messenger.auth.spi.DbAuthProvider, org.jivesoftware.messenger.auth.DefaultAuthProvider,
org.jivesoftware.messenger.user.spi.UserManagerImpl,
org.jivesoftware.messenger.spi.BasicServer" %> org.jivesoftware.messenger.spi.BasicServer" %>
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %> <%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %>
...@@ -76,19 +75,17 @@ ...@@ -76,19 +75,17 @@
if (errors.size() == 0) { if (errors.size() == 0) {
try { try {
// Get the service // Get the service
UserManager userManager = new UserManagerImpl(); UserManager userManager = UserManager.getInstance();
userManager.initialize(BasicServer.getInstance());
User adminUser = userManager.getUser("admin"); User adminUser = userManager.getUser("admin");
adminUser.setPassword(newPassword); adminUser.setPassword(newPassword);
if (email != null) { if (email != null) {
adminUser.getInfo().setEmail(email); adminUser.setEmail(email);
} }
Date now = new Date(); Date now = new Date();
adminUser.getInfo().setCreationDate(now); adminUser.setCreationDate(now);
adminUser.getInfo().setModificationDate(now); adminUser.setModificationDate(now);
adminUser.saveInfo();
// TODO: Check for Plugin // TODO: Check for Plugin
......
...@@ -5,8 +5,8 @@ ...@@ -5,8 +5,8 @@
--%> --%>
<%@ page import="org.jivesoftware.util.ParamUtils, <%@ page import="org.jivesoftware.util.ParamUtils,
java.util.Iterator, org.jivesoftware.messenger.user.User,
org.jivesoftware.messenger.user.User" java.util.*"
errorPage="error.jsp" errorPage="error.jsp"
%> %>
...@@ -168,8 +168,8 @@ ...@@ -168,8 +168,8 @@
<th align="center">Add</th> <th align="center">Add</th>
</tr> </tr>
<% // Print the list of users <% // Print the list of users
Iterator users = userManager.users(start, range); Collection<User> users = userManager.getUsers(start, range);
if (!users.hasNext()) { if (users.isEmpty()) {
%> %>
<tr> <tr>
<td align="center" colspan="4"> <td align="center" colspan="4">
...@@ -180,8 +180,7 @@ ...@@ -180,8 +180,7 @@
<% <%
} }
int i = start; int i = start;
while (users.hasNext()) { for (User user: users) {
User user = (User)users.next();
i++; i++;
%> %>
<tr class="jive-<%= (((i%2)==0) ? "even" : "odd") %>"> <tr class="jive-<%= (((i%2)==0) ? "even" : "odd") %>">
...@@ -192,7 +191,7 @@ ...@@ -192,7 +191,7 @@
<%= user.getUsername() %> <%= user.getUsername() %>
</td> </td>
<td width="50%"> <td width="50%">
<%= user.getInfo().getName() %> <%= user.getName() %>
</td> </td>
<td width="1%" align="center"> <td width="1%" align="center">
<input type="submit" name="" value="Add User" class="jive-sm-button" <input type="submit" name="" value="Add User" class="jive-sm-button"
......
...@@ -14,7 +14,6 @@ ...@@ -14,7 +14,6 @@
java.util.Map, java.util.Map,
org.jivesoftware.messenger.user.UserManager, org.jivesoftware.messenger.user.UserManager,
org.jivesoftware.messenger.user.*, org.jivesoftware.messenger.user.*,
org.jivesoftware.messenger.user.spi.*,
java.util.*, java.util.*,
org.jivesoftware.messenger.*, org.jivesoftware.messenger.*,
org.jivesoftware.admin.*, org.jivesoftware.admin.*,
...@@ -68,12 +67,8 @@ ...@@ -68,12 +67,8 @@
// do a create if there were no errors // do a create if there were no errors
if (errors.size() == 0) { if (errors.size() == 0) {
try { try {
User newUser = webManager.getUserManager().createUser(username, password, email); User newUser = webManager.getUserManager().createUser(username, password, name, email);
if (name != null) {
newUser.getInfo().setName(name);
}
newUser.saveInfo();
// Successful, so redirect // Successful, so redirect
response.sendRedirect("user-properties.jsp?success=true&username=" + newUser.getUsername()); response.sendRedirect("user-properties.jsp?success=true&username=" + newUser.getUsername());
return; return;
......
...@@ -50,9 +50,8 @@ ...@@ -50,9 +50,8 @@
errors.put("email","email"); errors.put("email","email");
} }
if (errors.size() == 0) { if (errors.size() == 0) {
user.getInfo().setEmail(email); user.setEmail(email);
user.getInfo().setName(name); user.setName(name);
user.saveInfo();
// Changes good, so redirect // Changes good, so redirect
response.sendRedirect("user-properties.jsp?editsuccess=true&username=" + username); response.sendRedirect("user-properties.jsp?editsuccess=true&username=" + username);
...@@ -116,7 +115,7 @@ Use the form below to edit user properties. ...@@ -116,7 +115,7 @@ Use the form below to edit user properties.
</td> </td>
<td> <td>
<input type="text" size="30" maxlength="150" name="name" <input type="text" size="30" maxlength="150" name="name"
value="<%= ((user.getInfo().getName()!=null) ? user.getInfo().getName() : "") %>"> value="<%= ((user.getName()!=null) ? user.getName() : "") %>">
<% if (errors.get("name") != null) { %> <% if (errors.get("name") != null) { %>
...@@ -133,7 +132,7 @@ Use the form below to edit user properties. ...@@ -133,7 +132,7 @@ Use the form below to edit user properties.
</td> </td>
<td> <td>
<input type="text" size="30" maxlength="150" name="email" <input type="text" size="30" maxlength="150" name="email"
value="<%= ((user.getInfo().getEmail()!=null) ? user.getInfo().getEmail() : "") %>"> value="<%= ((user.getEmail()!=null) ? user.getEmail() : "") %>">
<% if (errors.get("email") != null) { %> <% if (errors.get("email") != null) { %>
......
...@@ -7,8 +7,7 @@ ...@@ -7,8 +7,7 @@
<%@ page import="org.jivesoftware.util.*, <%@ page import="org.jivesoftware.util.*,
org.jivesoftware.messenger.user.UserManager, org.jivesoftware.messenger.user.UserManager,
java.text.DateFormat, java.text.DateFormat,
org.jivesoftware.messenger.user.User, org.jivesoftware.messenger.user.User"
org.jivesoftware.messenger.user.spi.UserManagerImpl"
errorPage="error.jsp" errorPage="error.jsp"
%> %>
...@@ -60,7 +59,7 @@ Use the form below to send an email to the user. ...@@ -60,7 +59,7 @@ Use the form below to send an email to the user.
From: From:
</td> </td>
<td> <td>
<%= pageUser.getUsername() %> &lt;<%= pageUser.getInfo().getEmail() %>&gt; <%= pageUser.getUsername() %> &lt;<%= pageUser.getEmail() %>&gt;
(<a href="user-edit-form.jsp?username=<%= user.getUsername() %>">Edit User</a>) (<a href="user-edit-form.jsp?username=<%= user.getUsername() %>">Edit User</a>)
</td> </td>
</tr> </tr>
...@@ -69,7 +68,7 @@ Use the form below to send an email to the user. ...@@ -69,7 +68,7 @@ Use the form below to send an email to the user.
TO: TO:
</td> </td>
<td> <td>
<input type="text" name="to" value="<%= user.getUsername() %> &lt;<%= user.getInfo().getEmail() %>&gt;" <input type="text" name="to" value="<%= user.getUsername() %> &lt;<%= user.getEmail() %>&gt;"
size="45" maxlength="150"> size="45" maxlength="150">
</td> </td>
</tr> </tr>
......
...@@ -173,7 +173,7 @@ Below is a summary of user properties. To edit properties, click the "Edit" butt ...@@ -173,7 +173,7 @@ Below is a summary of user properties. To edit properties, click the "Edit" butt
Name: Name:
</td> </td>
<td> <td>
<% if (user.getInfo().getName() == null || "".equals(user.getInfo().getName())) { %> <% if (user.getName() == null || "".equals(user.getName())) { %>
<span style="color:#999"> <span style="color:#999">
<i>Not set.</i> <i>Not set.</i>
...@@ -181,7 +181,7 @@ Below is a summary of user properties. To edit properties, click the "Edit" butt ...@@ -181,7 +181,7 @@ Below is a summary of user properties. To edit properties, click the "Edit" butt
<% } else { %> <% } else { %>
<%= user.getInfo().getName() %> <%= user.getName() %>
<% } %> <% } %>
</td> </td>
...@@ -191,7 +191,7 @@ Below is a summary of user properties. To edit properties, click the "Edit" butt ...@@ -191,7 +191,7 @@ Below is a summary of user properties. To edit properties, click the "Edit" butt
Email: Email:
</td> </td>
<td> <td>
<a href="mailto:<%= user.getInfo().getEmail() %>"><%= user.getInfo().getEmail() %></a> <a href="mailto:<%= user.getEmail() %>"><%= user.getEmail() %></a>
&nbsp; &nbsp;
</td> </td>
</tr> </tr>
...@@ -200,7 +200,7 @@ Below is a summary of user properties. To edit properties, click the "Edit" butt ...@@ -200,7 +200,7 @@ Below is a summary of user properties. To edit properties, click the "Edit" butt
Registered: Registered:
</td> </td>
<td> <td>
<%= formatter.format(user.getInfo().getCreationDate()) %> <%= formatter.format(user.getCreationDate()) %>
</td> </td>
</tr> </tr>
</tbody> </tbody>
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
<%@ page import="org.jivesoftware.util.*, <%@ page import="org.jivesoftware.util.*,
org.jivesoftware.messenger.user.*, org.jivesoftware.messenger.user.*,
java.util.Iterator, java.util.*,
org.jivesoftware.messenger.user.UserManager, org.jivesoftware.messenger.user.UserManager,
java.text.DateFormat, java.text.DateFormat,
org.jivesoftware.admin.*, org.jivesoftware.admin.*,
...@@ -117,8 +117,8 @@ Sorted by Username ...@@ -117,8 +117,8 @@ Sorted by Username
<tbody> <tbody>
<% // Print the list of users <% // Print the list of users
Iterator users = webManager.getUserManager().users(start, range); Collection<User> users = webManager.getUserManager().getUsers(start, range);
if (!users.hasNext()) { if (users.isEmpty()) {
%> %>
<tr> <tr>
<td align="center" colspan="7"> <td align="center" colspan="7">
...@@ -129,8 +129,7 @@ Sorted by Username ...@@ -129,8 +129,7 @@ Sorted by Username
<% <%
} }
int i = start; int i = start;
while (users.hasNext()) { for (User user : users) {
User user = (User)users.next();
i++; i++;
%> %>
<tr class="jive-<%= (((i%2)==0) ? "even" : "odd") %>"> <tr class="jive-<%= (((i%2)==0) ? "even" : "odd") %>">
...@@ -167,10 +166,10 @@ Sorted by Username ...@@ -167,10 +166,10 @@ Sorted by Username
<a href="user-properties.jsp?username=<%= user.getUsername() %>"><%= user.getUsername() %></a> <a href="user-properties.jsp?username=<%= user.getUsername() %>"><%= user.getUsername() %></a>
</td> </td>
<td width="40%"> <td width="40%">
<%= user.getInfo().getName() %> &nbsp; <%= user.getName() %> &nbsp;
</td> </td>
<td width="26%"> <td width="26%">
<%= dateFormatter.format(user.getInfo().getCreationDate()) %> <%= dateFormatter.format(user.getCreationDate()) %>
</td> </td>
<td width="1%" align="center"> <td width="1%" align="center">
<a href="user-edit-form.jsp?username=<%= user.getUsername() %>" <a href="user-edit-form.jsp?username=<%= user.getUsername() %>"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment