/** * $Revision: 1116 $ * $Date: 2005-03-10 20:18:08 -0300 (Thu, 10 Mar 2005) $ * * Copyright (C) 2006 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.wildfire.auth; import org.jivesoftware.util.Log; import org.jivesoftware.util.StringUtils; import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.wildfire.user.*; import org.jivesoftware.database.DbConnectionManager; import java.sql.DriverManager; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; /** * The JDBC auth provider allows you to authenticate users against any database * that you can connect to with JDBC. It can be used along with the * {@link HybridAuthProvider hybrid} auth provider, so that you can also have * XMPP-only users that won't pollute your external data.<p> * * To enable this provider, set the following in the XML configuration file: * * <pre> * <provider> * <auth> * <className>org.jivesoftware.wildfire.auth.JDBCAuthProvider</className> * </auth> * </provider> * </pre> * * You'll also need to set your JDBC driver, connection string, and SQL statements: * * <pre> * <jdbcProvider> * <driver>com.mysql.jdbc.Driver</driver> * <connectionString>jdbc:mysql://localhost/dbname?user=username&password=secret</connectionString> * </jdbcProvider> * * <jdbcAuthProvider> * <passwordSQL>SELECT password FROM user_account WHERE username=?<passwordSQL> * <passwordType>plain<passwordType> * </jdbcAuthProvider></pre> * * The passwordType setting tells Wildfire how the password is stored. Setting the value * is optional (when not set, it defaults to "plain"). The valid values are:<ul> * <li>{@link PasswordType#plain plain} * <li>{@link PasswordType#md5 md5} * <li>{@link PasswordType#sha1 sha1} * </ul> * * @author David Snopek */ public class JDBCAuthProvider implements AuthProvider { private String connectionString; private String passwordSQL; private PasswordType passwordType = PasswordType.plain; /** * Constructs a new JDBC authentication provider. */ public JDBCAuthProvider() { // Load the JDBC driver and connection string. String jdbcDriver = JiveGlobals.getXMLProperty("jdbcProvider.jdbcDriver.className"); try { Class.forName(jdbcDriver).newInstance(); } catch (Exception e) { Log.error("Unable to load JDBC driver: " + jdbcDriver, e); return; } connectionString = JiveGlobals.getXMLProperty("jdbcProvider.jdbcConnString"); // Load SQL statements. passwordSQL = JiveGlobals.getXMLProperty("jdbcAuthProvider.passwordSQL"); passwordType = PasswordType.plain; try { PasswordType.valueOf(JiveGlobals.getXMLProperty("jdbcAuthProvider.passwordType", "plain")); } catch (IllegalArgumentException iae) { Log.error(iae); } } public void authenticate(String username, String password) throws UnauthorizedException { if (username == null || password == null) { throw new UnauthorizedException(); } username = username.trim().toLowerCase(); String userPassword; try { userPassword = getPassword(username); } catch (UserNotFoundException unfe) { throw new UnauthorizedException(); } // If the user's password doesn't match the password passed in, authentication // should fail. if (passwordType == PasswordType.md5) { password = StringUtils.hash(password, "MD5"); } else if (passwordType == PasswordType.sha1) { password = StringUtils.hash(password, "SHA-1"); } if (!password.equals(userPassword)) { throw new UnauthorizedException(); } // Got this far, so the user must be authorized. createUser(username); } public void authenticate(String username, String token, String digest) throws UnauthorizedException { if (passwordType != PasswordType.plain) { throw new UnsupportedOperationException("Digest authentication not supported for " + "password type " + passwordType); } if (username == null || token == null || digest == null) { throw new UnauthorizedException(); } username = username.trim().toLowerCase(); String password; try { password = getPassword(username); } catch (UserNotFoundException unfe) { throw new UnauthorizedException(); } String anticipatedDigest = AuthFactory.createDigest(token, password); if (!digest.equalsIgnoreCase(anticipatedDigest)) { throw new UnauthorizedException(); } // Got this far, so the user must be authorized. createUser(username); } public boolean isPlainSupported() { // If the auth SQL is defined, plain text authentication is supported. return (passwordSQL != null); } public boolean isDigestSupported() { // The auth SQL must be defined and the password type is supported. return (passwordSQL != null && passwordType == PasswordType.plain); } public String getPassword(String username) throws UserNotFoundException, UnsupportedOperationException { if (!supportsPasswordRetrieval()) { throw new UnsupportedOperationException(); } String password = null; Connection con = null; PreparedStatement pstmt = null; ResultSet rs = null; try { con = DriverManager.getConnection(connectionString); pstmt = con.prepareStatement(passwordSQL); pstmt.setString(1, username); rs = pstmt.executeQuery(); // If the query had no results, the username and password // did not match a user record. Therefore, throw an exception. if (!rs.next()) { throw new UserNotFoundException(); } password = rs.getString(1); } catch (SQLException e) { Log.error("Exception in JDBCAuthProvider", e); throw new UserNotFoundException(); } finally { DbConnectionManager.closeConnection(rs, pstmt, con); } return password; } public void setPassword(String username, String password) throws UserNotFoundException, UnsupportedOperationException { throw new UnsupportedOperationException(); } public boolean supportsPasswordRetrieval() { return (passwordSQL != null && passwordType == PasswordType.plain); } /** * Indicates how the password is stored. */ public enum PasswordType { /** * The password is stored as plain text. */ plain, /** * The password is stored as a hex-encoded MD5 hash. */ md5, /** * The password is stored as a hex-encoded SHA-1 hash. */ sha1 } /** * Checks to see if the user exists; if not, a new user is created. * * @param username the username. */ private static void createUser(String username) { // See if the user exists in the database. If not, automatically create them. UserManager userManager = UserManager.getInstance(); try { userManager.getUser(username); } catch (UserNotFoundException unfe) { try { Log.debug("Automatically creating new user account for " + username); UserManager.getUserProvider().createUser(username, StringUtils.randomString(8), null, null); } catch (UserAlreadyExistsException uaee) { // Ignore. } } } }